diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 77ebe407..ca02e2d9 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -24,7 +24,7 @@ const certbotLogsDir = "/data/logs"; const certbotWorkDir = "/tmp/letsencrypt-lib"; const omissions = () => { - return ["is_deleted", "owner.is_deleted"]; + return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials"]; }; const internalCertificate = { @@ -122,7 +122,7 @@ const internalCertificate = { } // this command really should clean up and delete the cert if it can't fully succeed - const certificate = await certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); + const certificate = await certificateModel.query().insertAndFetch(data); try { if (certificate.provider === "letsencrypt") { @@ -202,6 +202,9 @@ const internalCertificate = { savedRow.meta = _.assign({}, savedRow.meta, { letsencrypt_certificate: certInfo, }); + + await internalCertificate.addCreatedAuditLog(access, certificate.id, savedRow); + return savedRow; } catch (err) { // Delete the certificate from the database if it was not created successfully @@ -218,14 +221,18 @@ const internalCertificate = { data.meta = _.assign({}, data.meta || {}, certificate.meta); // Add to audit log + await internalCertificate.addCreatedAuditLog(access, certificate.id, utils.omitRow(omissions())(data)); + + return utils.omitRow(omissions())(certificate); + }, + + addCreatedAuditLog: async (access, certificate_id, meta) => { await internalAuditLog.add(access, { action: "created", object_type: "certificate", - object_id: certificate.id, - meta: data, + object_id: certificate_id, + meta: meta, }); - - return certificate; }, /** @@ -285,10 +292,7 @@ const internalCertificate = { .query() .where("is_deleted", 0) .andWhere("id", data.id) - .allowGraph("[owner]") - .allowGraph("[proxy_hosts]") - .allowGraph("[redirection_hosts]") - .allowGraph("[dead_hosts]") + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") .first(); if (accessData.permission_visibility !== "all") { @@ -305,7 +309,24 @@ const internalCertificate = { } // Custom omissions if (typeof data.omit !== "undefined" && data.omit !== null) { - return _.omit(row, data.omit); + return _.omit(row, [...data.omit]); + } + + return internalCertificate.cleanExpansions(row); + }, + + cleanExpansions: (row) => { + if (typeof row.proxy_hosts !== "undefined") { + row.proxy_hosts = utils.omitRows(["is_deleted"])(row.proxy_hosts); + } + if (typeof row.redirection_hosts !== "undefined") { + row.redirection_hosts = utils.omitRows(["is_deleted"])(row.redirection_hosts); + } + if (typeof row.dead_hosts !== "undefined") { + row.dead_hosts = utils.omitRows(["is_deleted"])(row.dead_hosts); + } + if (typeof row.streams !== "undefined") { + row.streams = utils.omitRows(["is_deleted"])(row.streams); } return row; }, @@ -415,7 +436,7 @@ const internalCertificate = { .query() .where("is_deleted", 0) .groupBy("id") - .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts]") + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") .orderBy("nice_name", "ASC"); if (accessData.permission_visibility !== "all") { @@ -433,7 +454,11 @@ const internalCertificate = { query.withGraphFetched(`[${expand.join(", ")}]`); } - return await query.then(utils.omitRows(omissions())); + const r = await query.then(utils.omitRows(omissions())); + for (let i = 0; i < r.length; i++) { + r[i] = internalCertificate.cleanExpansions(r[i]); + } + return r; }, /** diff --git a/backend/models/certificate.js b/backend/models/certificate.js index 052d3187..9ad03c89 100644 --- a/backend/models/certificate.js +++ b/backend/models/certificate.js @@ -8,6 +8,7 @@ import deadHostModel from "./dead_host.js"; import now from "./now_helper.js"; import proxyHostModel from "./proxy_host.js"; import redirectionHostModel from "./redirection_host.js"; +import streamModel from "./stream.js"; import userModel from "./user.js"; Model.knex(db); @@ -114,6 +115,17 @@ class Certificate extends Model { qb.where("redirection_host.is_deleted", 0); }, }, + streams: { + relation: Model.HasManyRelation, + modelClass: streamModel, + join: { + from: "certificate.id", + to: "stream.certificate_id", + }, + modify: (qb) => { + qb.where("stream.is_deleted", 0); + }, + }, }; } } diff --git a/frontend/src/api/backend/expansions.ts b/frontend/src/api/backend/expansions.ts index 2f31e4d0..e098a490 100644 --- a/frontend/src/api/backend/expansions.ts +++ b/frontend/src/api/backend/expansions.ts @@ -1,6 +1,6 @@ export type AccessListExpansion = "owner" | "items" | "clients"; export type AuditLogExpansion = "user"; -export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts"; +export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts" | "streams"; export type HostExpansion = "owner" | "certificate"; export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; export type UserExpansion = "permissions"; diff --git a/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx b/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx index bb4c314f..7a9c592d 100644 --- a/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx +++ b/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx @@ -1,6 +1,6 @@ import OverlayTrigger from "react-bootstrap/OverlayTrigger"; import Popover from "react-bootstrap/Popover"; -import type { DeadHost, ProxyHost, RedirectionHost } from "src/api/backend"; +import type { DeadHost, ProxyHost, RedirectionHost, Stream } from "src/api/backend"; import { T } from "src/locale"; const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | DeadHost[]) => { @@ -23,13 +23,34 @@ const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | Dead ); }; +const getSectionStream = (items: Stream[]) => { + if (items.length === 0) { + return null; + } + return ( + <> +