mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 13:27:22 +00:00
Transfer cert endpoints to work with serial numbers
This commit is contained in:
@@ -23,6 +23,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.string("commonName").notNullable();
|
t.string("commonName").notNullable();
|
||||||
t.string("dn").notNullable();
|
t.string("dn").notNullable();
|
||||||
t.unique(["dn", "projectId"]);
|
t.unique(["dn", "projectId"]);
|
||||||
|
t.string("serialNumber").nullable().unique();
|
||||||
t.integer("maxPathLength").nullable();
|
t.integer("maxPathLength").nullable();
|
||||||
t.datetime("notBefore").nullable();
|
t.datetime("notBefore").nullable();
|
||||||
t.datetime("notAfter").nullable();
|
t.datetime("notAfter").nullable();
|
||||||
@@ -54,12 +55,14 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.Certificate))) {
|
if (!(await knex.schema.hasTable(TableName.Certificate))) {
|
||||||
// TODO: consider adding name
|
// TODO: consider adding serialNumber
|
||||||
await knex.schema.createTable(TableName.Certificate, (t) => {
|
await knex.schema.createTable(TableName.Certificate, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
t.uuid("caId").notNullable();
|
t.uuid("caId").notNullable();
|
||||||
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||||
|
t.string("status").notNullable(); // active / pending-certificate
|
||||||
|
t.string("serialNumber").notNullable().unique();
|
||||||
t.string("commonName").notNullable();
|
t.string("commonName").notNullable();
|
||||||
t.datetime("notBefore").notNullable();
|
t.datetime("notBefore").notNullable();
|
||||||
t.datetime("notAfter").notNullable();
|
t.datetime("notAfter").notNullable();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const CertificateAuthoritiesSchema = z.object({
|
|||||||
locality: z.string(),
|
locality: z.string(),
|
||||||
commonName: z.string(),
|
commonName: z.string(),
|
||||||
dn: z.string(),
|
dn: z.string(),
|
||||||
|
serialNumber: z.string().nullable().optional(),
|
||||||
maxPathLength: z.number().nullable().optional(),
|
maxPathLength: z.number().nullable().optional(),
|
||||||
notBefore: z.date().nullable().optional(),
|
notBefore: z.date().nullable().optional(),
|
||||||
notAfter: z.date().nullable().optional()
|
notAfter: z.date().nullable().optional()
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export const CertificatesSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
caId: z.string().uuid(),
|
caId: z.string().uuid(),
|
||||||
|
status: z.string(),
|
||||||
|
serialNumber: z.string(),
|
||||||
commonName: z.string(),
|
commonName: z.string(),
|
||||||
notBefore: z.date(),
|
notBefore: z.date(),
|
||||||
notAfter: z.date()
|
notAfter: z.date()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
export const registerCertRouter = async (server: FastifyZodProvider) => {
|
export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:certId",
|
url: "/:serialNumber",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -16,7 +16,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Get certificate",
|
description: "Get certificate",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certId: z.string().trim()
|
serialNumber: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -25,8 +25,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const certificate = await server.services.certificate.getCertById({
|
const certificate = await server.services.certificate.getCert({
|
||||||
certId: req.params.certId,
|
serialNumber: req.params.serialNumber,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -38,9 +38,45 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:serialNumber/revoke",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Revoke",
|
||||||
|
params: z.object({
|
||||||
|
serialNumber: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string().trim(),
|
||||||
|
serialNumber: z.string().trim(),
|
||||||
|
revokedAt: z.date()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
await server.services.certificate.revokeCert({
|
||||||
|
serialNumber: req.params.serialNumber,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
message: "Successfully revoked certificate",
|
||||||
|
serialNumber: req.params.serialNumber,
|
||||||
|
revokedAt: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:certId",
|
url: "/:serialNumber",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
@@ -48,7 +84,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Delete certificate",
|
description: "Delete certificate",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certId: z.string().trim()
|
serialNumber: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -57,8 +93,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const certificate = await server.services.certificate.deleteCertById({
|
const certificate = await server.services.certificate.deleteCert({
|
||||||
certId: req.params.certId,
|
serialNumber: req.params.serialNumber,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -72,7 +108,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:certId/certificate",
|
url: "/:serialNumber/certificate",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -80,7 +116,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Get certificate of certificate",
|
description: "Get certificate of certificate",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certId: z.string().trim()
|
serialNumber: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -92,7 +128,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { certificate, certificateChain, serialNumber } = await server.services.certificate.getCertCert({
|
const { certificate, certificateChain, serialNumber } = await server.services.certificate.getCertCert({
|
||||||
certId: req.params.certId,
|
serialNumber: req.params.serialNumber,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
|||||||
@@ -60,8 +60,13 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
|
|
||||||
await server.register(registerCaRouter, { prefix: "/ca" });
|
await server.register(
|
||||||
await server.register(registerCertRouter, { prefix: "/certificates" });
|
async (pkiRouter) => {
|
||||||
|
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
|
||||||
|
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
|
||||||
|
},
|
||||||
|
{ prefix: "/pki" }
|
||||||
|
);
|
||||||
|
|
||||||
await server.register(registerProjectBotRouter, { prefix: "/bot" });
|
await server.register(registerProjectBotRouter, { prefix: "/bot" });
|
||||||
await server.register(registerIntegrationRouter, { prefix: "/integration" });
|
await server.register(registerIntegrationRouter, { prefix: "/integration" });
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-bitwise */
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import crypto, { KeyObject } from "crypto";
|
import crypto, { KeyObject } from "crypto";
|
||||||
@@ -9,6 +10,7 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
|
|||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TCertStatus } from "../certificate/certificate-types";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { createDistinguishedName } from "./certificate-authority-fns";
|
import { createDistinguishedName } from "./certificate-authority-fns";
|
||||||
@@ -118,6 +120,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
? new Date(notAfter)
|
? new Date(notAfter)
|
||||||
: new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
: new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||||
|
|
||||||
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const ca = await certificateAuthorityDAL.create(
|
const ca = await certificateAuthorityDAL.create(
|
||||||
{
|
{
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
@@ -130,7 +133,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
commonName,
|
commonName,
|
||||||
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
|
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
|
||||||
dn,
|
dn,
|
||||||
...(type === CaType.ROOT && { maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate })
|
...(type === CaType.ROOT && { maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate, serialNumber })
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -140,6 +143,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||||
name: dn,
|
name: dn,
|
||||||
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate,
|
notAfter: notAfterDate,
|
||||||
signingAlgorithm: alg,
|
signingAlgorithm: alg,
|
||||||
@@ -291,7 +295,12 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
signingAlgorithm: alg,
|
signingAlgorithm: alg,
|
||||||
extensions: [
|
extensions: [
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
new x509.KeyUsagesExtension(
|
||||||
|
x509.KeyUsageFlags.keyCertSign |
|
||||||
|
x509.KeyUsageFlags.cRLSign |
|
||||||
|
x509.KeyUsageFlags.digitalSignature |
|
||||||
|
x509.KeyUsageFlags.keyEncipherment
|
||||||
|
)
|
||||||
],
|
],
|
||||||
attributes: [new x509.ChallengePasswordAttribute("password")]
|
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||||
});
|
});
|
||||||
@@ -411,8 +420,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const intermediateCert = await x509.X509CertificateGenerator.create({
|
const intermediateCert = await x509.X509CertificateGenerator.create({
|
||||||
// serialNumber: "03",
|
serialNumber,
|
||||||
subject: csrObj.subject,
|
subject: csrObj.subject,
|
||||||
issuer: certObj.subject,
|
issuer: certObj.subject,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
@@ -421,7 +431,13 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
publicKey: csrObj.publicKey,
|
publicKey: csrObj.publicKey,
|
||||||
signingAlgorithm: alg,
|
signingAlgorithm: alg,
|
||||||
extensions: [
|
extensions: [
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.dataEncipherment, true),
|
new x509.KeyUsagesExtension(
|
||||||
|
x509.KeyUsageFlags.keyCertSign |
|
||||||
|
x509.KeyUsageFlags.cRLSign |
|
||||||
|
x509.KeyUsageFlags.digitalSignature |
|
||||||
|
x509.KeyUsageFlags.keyEncipherment,
|
||||||
|
true
|
||||||
|
),
|
||||||
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(certObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(certObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
@@ -511,6 +527,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
maxPathLength: maxPathLength === undefined ? -1 : maxPathLength,
|
maxPathLength: maxPathLength === undefined ? -1 : maxPathLength,
|
||||||
notBefore: new Date(certObj.notBefore),
|
notBefore: new Date(certObj.notBefore),
|
||||||
notAfter: new Date(certObj.notAfter),
|
notAfter: new Date(certObj.notAfter),
|
||||||
|
serialNumber: certObj.serialNumber,
|
||||||
parentCaId: parentCa?.id
|
parentCaId: parentCa?.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -601,8 +618,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const leafCert = await x509.X509CertificateGenerator.create({
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
// serialNumber: "03",
|
serialNumber,
|
||||||
subject: csrObj.subject,
|
subject: csrObj.subject,
|
||||||
issuer: caCertObj.subject,
|
issuer: caCertObj.subject,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
@@ -611,7 +629,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
publicKey: csrObj.publicKey,
|
publicKey: csrObj.publicKey,
|
||||||
signingAlgorithm: alg,
|
signingAlgorithm: alg,
|
||||||
extensions: [
|
extensions: [
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.dataEncipherment, true),
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
||||||
new x509.BasicConstraintsExtension(false),
|
new x509.BasicConstraintsExtension(false),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
@@ -627,7 +645,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const cert = await certificateDAL.create(
|
const cert = await certificateDAL.create(
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
|
status: TCertStatus.ACTIVE,
|
||||||
commonName,
|
commonName,
|
||||||
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate
|
notAfter: notAfterDate
|
||||||
},
|
},
|
||||||
@@ -651,7 +671,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateChain: chain.join("\n"),
|
certificateChain: chain.join("\n"),
|
||||||
issuingCaCertificate: caCert.certificate,
|
issuingCaCertificate: caCert.certificate,
|
||||||
privateKey: skLeaf,
|
privateKey: skLeaf,
|
||||||
serialNumber: leafCert.serialNumber
|
serialNumber
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ export enum CaStatus {
|
|||||||
PENDING_CERTIFICATE = "pending-certificate"
|
PENDING_CERTIFICATE = "pending-certificate"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: attach permissions after draft impl
|
|
||||||
|
|
||||||
export type TCreateCaDTO = {
|
export type TCreateCaDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
type: CaType;
|
type: CaType;
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
|
|||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
|
|
||||||
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO } from "./certificate-types";
|
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
|
||||||
|
|
||||||
type TCertificateServiceFactoryDep = {
|
type TCertificateServiceFactoryDep = {
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "findById" | "deleteById">;
|
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById">;
|
||||||
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
|
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -24,8 +24,8 @@ export const certificateServiceFactory = ({
|
|||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
permissionService
|
permissionService
|
||||||
}: TCertificateServiceFactoryDep) => {
|
}: TCertificateServiceFactoryDep) => {
|
||||||
const getCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
const getCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
||||||
const cert = await certificateDAL.findById(certId);
|
const cert = await certificateDAL.findOne({ serialNumber });
|
||||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -41,8 +41,8 @@ export const certificateServiceFactory = ({
|
|||||||
return cert;
|
return cert;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertDTO) => {
|
const deleteCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertDTO) => {
|
||||||
const cert = await certificateDAL.findById(certId);
|
const cert = await certificateDAL.findOne({ serialNumber });
|
||||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -59,8 +59,31 @@ export const certificateServiceFactory = ({
|
|||||||
return deletedCert;
|
return deletedCert;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCertCert = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
|
const revokeCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TRevokeCertDTO) => {
|
||||||
const cert = await certificateDAL.findById(certId);
|
const cert = await certificateDAL.findOne({ serialNumber });
|
||||||
|
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||||
|
// WIP
|
||||||
|
|
||||||
|
// const revocationDate = new Date();
|
||||||
|
|
||||||
|
// const serialNumber2 = crypto.randomBytes(16).toString("hex");
|
||||||
|
// const crlEntry = new x509.X509CrlEntry(serialNumber2, new Date(), []);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
|
||||||
|
const cert = await certificateDAL.findOne({ serialNumber });
|
||||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -73,7 +96,7 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
const certCert = await certificateCertDAL.findOne({ certId });
|
const certCert = await certificateCertDAL.findOne({ certId: cert.id });
|
||||||
const certObj = new x509.X509Certificate(certCert.certificate);
|
const certObj = new x509.X509Certificate(certCert.certificate);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -84,8 +107,9 @@ export const certificateServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCertById,
|
getCert,
|
||||||
deleteCertById,
|
deleteCert,
|
||||||
|
revokeCert,
|
||||||
getCertCert
|
getCertCert
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum TCertStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
REVOKED = "revoked"
|
||||||
|
}
|
||||||
|
|
||||||
export type TGetCertDTO = {
|
export type TGetCertDTO = {
|
||||||
certId: string;
|
serialNumber: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteCertDTO = {
|
export type TDeleteCertDTO = {
|
||||||
certId: string;
|
serialNumber: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRevokeCertDTO = {
|
||||||
|
serialNumber: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCertCertDTO = {
|
export type TGetCertCertDTO = {
|
||||||
certId: string;
|
serialNumber: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: TCertificateDALFactory;
|
certificateDAL: Pick<TCertificateDALFactory, "find">;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
TImportCaCertificateResponse,
|
TImportCaCertificateResponse,
|
||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
TSignIntermediateResponse,
|
TSignIntermediateResponse,
|
||||||
TUpdateCaDTO} from "./types";
|
TUpdateCaDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export const useCreateCa = () => {
|
export const useCreateCa = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -21,7 +22,7 @@ export const useCreateCa = () => {
|
|||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
const {
|
const {
|
||||||
data: { ca }
|
data: { ca }
|
||||||
} = await apiRequest.post<{ ca: TCertificateAuthority }>("/api/v1/ca/", body);
|
} = await apiRequest.post<{ ca: TCertificateAuthority }>("/api/v1/pki/ca/", body);
|
||||||
return ca;
|
return ca;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
@@ -36,7 +37,7 @@ export const useUpdateCa = () => {
|
|||||||
mutationFn: async ({ caId, projectSlug, ...body }) => {
|
mutationFn: async ({ caId, projectSlug, ...body }) => {
|
||||||
const {
|
const {
|
||||||
data: { ca }
|
data: { ca }
|
||||||
} = await apiRequest.patch<{ ca: TCertificateAuthority }>(`/api/v1/ca/${caId}`, body);
|
} = await apiRequest.patch<{ ca: TCertificateAuthority }>(`/api/v1/pki/ca/${caId}`, body);
|
||||||
return ca;
|
return ca;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
@@ -51,7 +52,7 @@ export const useDeleteCa = () => {
|
|||||||
mutationFn: async ({ caId }) => {
|
mutationFn: async ({ caId }) => {
|
||||||
const {
|
const {
|
||||||
data: { ca }
|
data: { ca }
|
||||||
} = await apiRequest.delete<{ ca: TCertificateAuthority }>(`/api/v1/ca/${caId}`);
|
} = await apiRequest.delete<{ ca: TCertificateAuthority }>(`/api/v1/pki/ca/${caId}`);
|
||||||
return ca;
|
return ca;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
@@ -65,7 +66,7 @@ export const useSignIntermediate = () => {
|
|||||||
return useMutation<TSignIntermediateResponse, {}, TSignIntermediateDTO>({
|
return useMutation<TSignIntermediateResponse, {}, TSignIntermediateDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
const { data } = await apiRequest.post<TSignIntermediateResponse>(
|
const { data } = await apiRequest.post<TSignIntermediateResponse>(
|
||||||
`/api/v1/ca/${body.caId}/sign-intermediate`,
|
`/api/v1/pki/ca/${body.caId}/sign-intermediate`,
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
@@ -78,7 +79,7 @@ export const useImportCaCertificate = () => {
|
|||||||
return useMutation<TImportCaCertificateResponse, {}, TImportCaCertificateDTO>({
|
return useMutation<TImportCaCertificateResponse, {}, TImportCaCertificateDTO>({
|
||||||
mutationFn: async ({ caId, ...body }) => {
|
mutationFn: async ({ caId, ...body }) => {
|
||||||
const { data } = await apiRequest.post<TImportCaCertificateResponse>(
|
const { data } = await apiRequest.post<TImportCaCertificateResponse>(
|
||||||
`/api/v1/ca/${caId}/import-certificate`,
|
`/api/v1/pki/ca/${caId}/import-certificate`,
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
@@ -95,7 +96,7 @@ export const useCreateCertificate = () => {
|
|||||||
return useMutation<TCreateCertificateResponse, {}, TCreateCertificateDTO>({
|
return useMutation<TCreateCertificateResponse, {}, TCreateCertificateDTO>({
|
||||||
mutationFn: async ({ caId, ...body }) => {
|
mutationFn: async ({ caId, ...body }) => {
|
||||||
const { data } = await apiRequest.post<TCreateCertificateResponse>(
|
const { data } = await apiRequest.post<TCreateCertificateResponse>(
|
||||||
`/api/v1/ca/${caId}/issue-certificate`,
|
`/api/v1/pki/ca/${caId}/issue-certificate`,
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const useGetCaById = (caId: string) => {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {
|
const {
|
||||||
data: { ca }
|
data: { ca }
|
||||||
} = await apiRequest.get<{ ca: TCertificateAuthority }>(`/api/v1/ca/${caId}`);
|
} = await apiRequest.get<{ ca: TCertificateAuthority }>(`/api/v1/pki/ca/${caId}`);
|
||||||
return ca;
|
return ca;
|
||||||
},
|
},
|
||||||
enabled: Boolean(caId)
|
enabled: Boolean(caId)
|
||||||
@@ -31,7 +31,7 @@ export const useGetCaCert = (caId: string) => {
|
|||||||
certificate: string;
|
certificate: string;
|
||||||
certificateChain: string;
|
certificateChain: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
}>(`/api/v1/ca/${caId}/certificate`);
|
}>(`/api/v1/pki/ca/${caId}/certificate`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: Boolean(caId)
|
enabled: Boolean(caId)
|
||||||
@@ -46,7 +46,7 @@ export const useGetCaCsr = (caId: string) => {
|
|||||||
data: { csr }
|
data: { csr }
|
||||||
} = await apiRequest.get<{
|
} = await apiRequest.get<{
|
||||||
csr: string;
|
csr: string;
|
||||||
}>(`/api/v1/ca/${caId}/csr`);
|
}>(`/api/v1/pki/ca/${caId}/csr`);
|
||||||
return csr;
|
return csr;
|
||||||
},
|
},
|
||||||
enabled: Boolean(caId)
|
enabled: Boolean(caId)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { CertStatus } from "./enums";
|
||||||
|
|
||||||
|
export const certStatusToNameMap: { [K in CertStatus]: string } = {
|
||||||
|
[CertStatus.ACTIVE]: "Active",
|
||||||
|
[CertStatus.REVOKED]: "Revoked"
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum CertStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
REVOKED = "revoked"
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export { useDeleteCert } from "./mutations";
|
export { useDeleteCert, useRevokeCert } from "./mutations";
|
||||||
export { useGetCertById, useGetCertCert } from "./queries";
|
export { useGetCert, useGetCertCert } from "./queries";
|
||||||
|
|||||||
@@ -3,20 +3,34 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace/queries";
|
import { workspaceKeys } from "../workspace/queries";
|
||||||
import { TCertificate } from "./types";
|
import { TCertificate, TDeleteCertDTO, TRevokeCertDTO } from "./types";
|
||||||
|
|
||||||
export type TDeleteCaDTO = {
|
|
||||||
projectSlug: string;
|
|
||||||
certId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteCert = () => {
|
export const useDeleteCert = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<TCertificate, {}, TDeleteCaDTO>({
|
return useMutation<TCertificate, {}, TDeleteCertDTO>({
|
||||||
mutationFn: async ({ certId }) => {
|
mutationFn: async ({ serialNumber }) => {
|
||||||
const {
|
const {
|
||||||
data: { certificate }
|
data: { certificate }
|
||||||
} = await apiRequest.delete<{ certificate: TCertificate }>(`/api/v1/certificates/${certId}`);
|
} = await apiRequest.delete<{ certificate: TCertificate }>(
|
||||||
|
`/api/v1/pki/certificates/${serialNumber}`
|
||||||
|
);
|
||||||
|
return certificate;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { projectSlug }) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificates(projectSlug));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRevokeCert = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TCertificate, {}, TRevokeCertDTO>({
|
||||||
|
mutationFn: async ({ serialNumber }) => {
|
||||||
|
const {
|
||||||
|
data: { certificate }
|
||||||
|
} = await apiRequest.post<{ certificate: TCertificate }>(
|
||||||
|
`/api/v1/pki/certificates/${serialNumber}/revoke`
|
||||||
|
);
|
||||||
return certificate;
|
return certificate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
|
|||||||
@@ -5,34 +5,36 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { TCertificate } from "./types";
|
import { TCertificate } from "./types";
|
||||||
|
|
||||||
export const certKeys = {
|
export const certKeys = {
|
||||||
getCertById: (certId: string) => [{ certId }, "cert"],
|
getCertById: (serialNumber: string) => [{ serialNumber }, "cert"],
|
||||||
getCertCert: (certId: string) => [{ certId }, "certCert"]
|
getCertCert: (serialNumber: string) => [{ serialNumber }, "certCert"]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetCertById = (certId: string) => {
|
export const useGetCert = (serialNumber: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: certKeys.getCertById(certId),
|
queryKey: certKeys.getCertById(serialNumber),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {
|
const {
|
||||||
data: { certificate }
|
data: { certificate }
|
||||||
} = await apiRequest.get<{ certificate: TCertificate }>(`/api/v1/certificates/${certId}`);
|
} = await apiRequest.get<{ certificate: TCertificate }>(
|
||||||
|
`/api/v1/pki/certificates/${serialNumber}`
|
||||||
|
);
|
||||||
return certificate;
|
return certificate;
|
||||||
},
|
},
|
||||||
enabled: Boolean(certId)
|
enabled: Boolean(serialNumber)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetCertCert = (certId: string) => {
|
export const useGetCertCert = (serialNumber: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: certKeys.getCertCert(certId),
|
queryKey: certKeys.getCertCert(serialNumber),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
certificate: string;
|
certificate: string;
|
||||||
certificateChain: string;
|
certificateChain: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
}>(`/api/v1/certificates/${certId}/certificate`);
|
}>(`/api/v1/pki/certificates/${serialNumber}/certificate`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: Boolean(certId)
|
enabled: Boolean(serialNumber)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
|
import { CertStatus } from "./enums";
|
||||||
|
|
||||||
export type TCertificate = {
|
export type TCertificate = {
|
||||||
id: string;
|
id: string;
|
||||||
caId: string;
|
caId: string;
|
||||||
|
status: CertStatus;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
|
serialNumber: string;
|
||||||
notBefore: string;
|
notBefore: string;
|
||||||
notAfter: string;
|
notAfter: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDeleteCertDTO = {
|
||||||
|
projectSlug: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TRevokeCertDTO = {
|
||||||
|
projectSlug: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ type Props = {
|
|||||||
|
|
||||||
export const CertificateCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
export const CertificateCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
const { data } = useGetCertCert(
|
const { data } = useGetCertCert(
|
||||||
(popUp?.certificateCert?.data as { certId: string })?.certId || ""
|
(popUp?.certificateCert?.data as { serialNumber: string })?.serialNumber || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+5
-1
@@ -111,6 +111,8 @@ export const CertificateContent = ({
|
|||||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
<p className="mr-4 whitespace-pre-wrap break-all">{certificate}</p>
|
<p className="mr-4 whitespace-pre-wrap break-all">{certificate}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{certificateChain && (
|
||||||
|
<>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2>Certificate Chain</h2>
|
<h2>Certificate Chain</h2>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@@ -133,7 +135,7 @@ export const CertificateContent = ({
|
|||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
className="group relative ml-2"
|
className="group relative ml-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
downloadTxtFile("certificate_chain.txt", certificate);
|
downloadTxtFile("certificate_chain.txt", certificateChain);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faDownload} />
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
@@ -146,6 +148,8 @@ export const CertificateContent = ({
|
|||||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
<p className="mr-4 whitespace-pre-wrap break-all">{certificateChain}</p>
|
<p className="mr-4 whitespace-pre-wrap break-all">{certificateChain}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{privateKey && (
|
{privateKey && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
|||||||
+3
-8
@@ -15,12 +15,7 @@ import {
|
|||||||
SelectItem
|
SelectItem
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import {
|
import { CaStatus, useCreateCertificate, useGetCert, useListWorkspaceCas } from "@app/hooks/api";
|
||||||
CaStatus,
|
|
||||||
useCreateCertificate,
|
|
||||||
useGetCertById,
|
|
||||||
useListWorkspaceCas
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -60,8 +55,8 @@ type TCertificateDetails = {
|
|||||||
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
|
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: cert } = useGetCertById(
|
const { data: cert } = useGetCert(
|
||||||
(popUp?.certificate?.data as { certId: string })?.certId || ""
|
(popUp?.certificate?.data as { serialNumber: string })?.serialNumber || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: cas } = useListWorkspaceCas({
|
const { data: cas } = useListWorkspaceCas({
|
||||||
|
|||||||
+54
-11
@@ -2,9 +2,10 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { useDeleteCert } from "@app/hooks/api";
|
import { useDeleteCert, useRevokeCert } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { CertificateCertModal } from "./CertificateCertModal";
|
import { CertificateCertModal } from "./CertificateCertModal";
|
||||||
@@ -14,18 +15,20 @@ import { CertificatesTable } from "./CertificatesTable";
|
|||||||
export const CertificatesSection = () => {
|
export const CertificatesSection = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { mutateAsync: deleteCert } = useDeleteCert();
|
const { mutateAsync: deleteCert } = useDeleteCert();
|
||||||
|
const { mutateAsync: revokeCert } = useRevokeCert();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"certificate",
|
"certificate",
|
||||||
"certificateCert",
|
"certificateCert",
|
||||||
"deleteCertificate"
|
"deleteCertificate",
|
||||||
|
"revokeCertificate"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const onRemoveCertificateSubmit = async (certId: string) => {
|
const onRemoveCertificateSubmit = async (serialNumber: string) => {
|
||||||
try {
|
try {
|
||||||
if (!currentWorkspace?.slug) return;
|
if (!currentWorkspace?.slug) return;
|
||||||
|
|
||||||
await deleteCert({ certId, projectSlug: currentWorkspace.slug });
|
await deleteCert({ serialNumber, projectSlug: currentWorkspace.slug });
|
||||||
|
|
||||||
await createNotification({
|
await createNotification({
|
||||||
text: "Successfully deleted certificate",
|
text: "Successfully deleted certificate",
|
||||||
@@ -42,23 +45,47 @@ export const CertificatesSection = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onRevokeCertificateSubmit = async (serialNumber: string) => {
|
||||||
|
try {
|
||||||
|
if (!currentWorkspace?.slug) return;
|
||||||
|
|
||||||
|
await revokeCert({ serialNumber, projectSlug: currentWorkspace.slug });
|
||||||
|
|
||||||
|
await createNotification({
|
||||||
|
text: "Successfully revoked certificate",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("revokeCertificate");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to revoke certificate",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
||||||
{/* <OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
|
<ProjectPermissionCan
|
||||||
{(isAllowed) => ( */}
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.Certificates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => handlePopUpOpen("certificate")}
|
onClick={() => handlePopUpOpen("certificate")}
|
||||||
// isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Issue Certificate
|
Issue Certificate
|
||||||
</Button>
|
</Button>
|
||||||
{/* )} */}
|
)}
|
||||||
{/* </OrgPermissionCan> */}
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
<CertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<CertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
@@ -71,7 +98,23 @@ export const CertificatesSection = () => {
|
|||||||
onChange={(isOpen) => handlePopUpToggle("deleteCertificate", isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("deleteCertificate", isOpen)}
|
||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() =>
|
onDeleteApproved={() =>
|
||||||
onRemoveCertificateSubmit((popUp?.deleteCertificate?.data as { certId: string })?.certId)
|
onRemoveCertificateSubmit(
|
||||||
|
(popUp?.deleteCertificate?.data as { serialNumber: string })?.serialNumber
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.revokeCertificate.isOpen}
|
||||||
|
title={`Are you sure want to revoke the certificate ${
|
||||||
|
(popUp?.revokeCertificate?.data as { commonName: string })?.commonName || ""
|
||||||
|
} from the project?`}
|
||||||
|
subTitle="This action is irreversible and will add the certificate to the CRL"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("revokeCertificate", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onRevokeCertificateSubmit(
|
||||||
|
(popUp?.revokeCertificate?.data as { serialNumber: string }).serialNumber
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+34
-8
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
faBan,
|
||||||
faCertificate,
|
faCertificate,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faEye,
|
faEye,
|
||||||
@@ -24,16 +25,20 @@ import {
|
|||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Tr} from "@app/components/v2";
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { useListWorkspaceCertificates } from "@app/hooks/api";
|
import { useListWorkspaceCertificates } from "@app/hooks/api";
|
||||||
|
import { certStatusToNameMap } from "@app/hooks/api/certificates/constants";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
popUpName: keyof UsePopUpState<["certificate", "deleteCertificate", "certificateCert"]>,
|
popUpName: keyof UsePopUpState<
|
||||||
|
["certificate", "deleteCertificate", "revokeCertificate", "certificateCert"]
|
||||||
|
>,
|
||||||
data?: {
|
data?: {
|
||||||
certId?: string;
|
serialNumber?: string;
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
@@ -48,8 +53,8 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Certificate ID</Th>
|
|
||||||
<Th>Common Name</Th>
|
<Th>Common Name</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
<Th>Valid Until</Th>
|
<Th>Valid Until</Th>
|
||||||
<Th />
|
<Th />
|
||||||
</Tr>
|
</Tr>
|
||||||
@@ -62,8 +67,8 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
data.map((certificate) => {
|
data.map((certificate) => {
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||||
<Td>{certificate.id}</Td>
|
|
||||||
<Td>{certificate.commonName}</Td>
|
<Td>{certificate.commonName}</Td>
|
||||||
|
<Td>{certStatusToNameMap[certificate.status]}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{certificate.notAfter
|
{certificate.notAfter
|
||||||
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
|
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
|
||||||
@@ -90,7 +95,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
handlePopUpOpen("certificateCert", {
|
handlePopUpOpen("certificateCert", {
|
||||||
certId: certificate.id
|
serialNumber: certificate.serialNumber
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
@@ -111,7 +116,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
handlePopUpOpen("certificate", {
|
handlePopUpOpen("certificate", {
|
||||||
certId: certificate.id
|
serialNumber: certificate.serialNumber
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
@@ -121,6 +126,27 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.Certificates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={async () =>
|
||||||
|
handlePopUpOpen("revokeCertificate", {
|
||||||
|
serialNumber: certificate.serialNumber
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faBan} />}
|
||||||
|
>
|
||||||
|
Revoke Certificate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={ProjectPermissionSub.Certificates}
|
||||||
@@ -132,7 +158,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
handlePopUpOpen("deleteCertificate", {
|
handlePopUpOpen("deleteCertificate", {
|
||||||
certId: certificate.id,
|
serialNumber: certificate.serialNumber,
|
||||||
commonName: certificate.commonName
|
commonName: certificate.commonName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user