mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-27 07:40:07 +02:00
✨ get batched certificates endpoint
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { certificates, db, domains } from "@server/db";
|
import { certificates, db, domains, orgDomains } from "@server/db";
|
||||||
import { eq, and, or, like, inArray } from "drizzle-orm";
|
import { eq, and, or, like, inArray } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -20,9 +20,16 @@ import createHttpError from "http-errors";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { GetCertificateResponse } from "@server/routers/certificates/types";
|
import {
|
||||||
|
GetCertificateResponse,
|
||||||
|
type GetBatchedCertificateResponse
|
||||||
|
} from "@server/routers/certificates/types";
|
||||||
|
|
||||||
const getCertificateSchema = z.strictObject({
|
const getCertificateParamSchema = z.strictObject({
|
||||||
|
orgId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCertificateQuerySchema = z.object({
|
||||||
domains: z.preprocess(
|
domains: z.preprocess(
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val === undefined || val === null || val === "") {
|
if (val === undefined || val === null || val === "") {
|
||||||
@@ -38,11 +45,10 @@ const getCertificateSchema = z.strictObject({
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
z.array(z.string().min(1).max(255))
|
z.array(z.string().min(1).max(255))
|
||||||
),
|
)
|
||||||
orgId: z.string()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function query(domainId: string, domain: string) {
|
async function query1(domainId: string, domain: string) {
|
||||||
const [domainRecord] = await db
|
const [domainRecord] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(domains)
|
.from(domains)
|
||||||
@@ -116,7 +122,7 @@ async function query(domainId: string, domain: string) {
|
|||||||
return existing.length > 0 ? { ...existing[0], domainType } : null;
|
return existing.length > 0 ? { ...existing[0], domainType } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function query2(domainList: string[]) {
|
async function query(orgId: string, domainList: string[]) {
|
||||||
// Try to get CNAME certificates first
|
// Try to get CNAME certificates first
|
||||||
let existingCertificates = await db
|
let existingCertificates = await db
|
||||||
.select({
|
.select({
|
||||||
@@ -129,31 +135,39 @@ async function query2(domainList: string[]) {
|
|||||||
createdAt: certificates.createdAt,
|
createdAt: certificates.createdAt,
|
||||||
updatedAt: certificates.updatedAt,
|
updatedAt: certificates.updatedAt,
|
||||||
errorMessage: certificates.errorMessage,
|
errorMessage: certificates.errorMessage,
|
||||||
renewalCount: certificates.renewalCount
|
renewalCount: certificates.renewalCount,
|
||||||
|
domainType: domains.type
|
||||||
})
|
})
|
||||||
.from(certificates)
|
.from(certificates)
|
||||||
.innerJoin(domains, eq(certificates.domainId, domains.domainId))
|
.innerJoin(domains, eq(certificates.domainId, domains.domainId))
|
||||||
|
.innerJoin(
|
||||||
|
orgDomains,
|
||||||
|
and(
|
||||||
|
eq(domains.domainId, orgDomains.domainId),
|
||||||
|
eq(orgDomains.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
.where(and(inArray(certificates.domain, domainList)));
|
.where(and(inArray(certificates.domain, domainList)));
|
||||||
|
|
||||||
// All non resolved domain certificates might be `ns` or `wildcard`
|
// All non resolved domain certificates might be `ns` or `wildcard`,
|
||||||
|
// which means exact domain certificates do not
|
||||||
const nonAvailableCertificates = existingCertificates
|
const nonAvailableCertificates = existingCertificates
|
||||||
.filter((cert) => !domainList.includes(cert.domain))
|
.filter((cert) => !domainList.includes(cert.domain))
|
||||||
.map((cert) => cert.domain);
|
.map((cert) => cert.domain);
|
||||||
|
|
||||||
if (nonAvailableCertificates.length > 0) {
|
if (nonAvailableCertificates.length > 0) {
|
||||||
const wildcardPrefixedDomains = nonAvailableCertificates.map(
|
const domainLevelDownSet = new Set<string>();
|
||||||
(domain) => {
|
const wildcardDomainSet = new Set<string>();
|
||||||
const domainLevelDown = domain.split(".").slice(1).join(".");
|
|
||||||
const wildcardPrefixed = `*.${domainLevelDown}`;
|
for (const domain of nonAvailableCertificates) {
|
||||||
return {
|
const domainLevelDown = domain.split(".").slice(1).join(".");
|
||||||
domainLevelDown,
|
const wildcardPrefixed = `*.${domainLevelDown}`;
|
||||||
wildcardPrefixed
|
domainLevelDownSet.add(domainLevelDown);
|
||||||
};
|
wildcardDomainSet.add(wildcardPrefixed);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// Need to map the certificates to each domain
|
// Need to map the certificates to each domain
|
||||||
await db
|
const wildcardCertificates = await db
|
||||||
.select({
|
.select({
|
||||||
certId: certificates.certId,
|
certId: certificates.certId,
|
||||||
domain: certificates.domain,
|
domain: certificates.domain,
|
||||||
@@ -164,33 +178,54 @@ async function query2(domainList: string[]) {
|
|||||||
createdAt: certificates.createdAt,
|
createdAt: certificates.createdAt,
|
||||||
updatedAt: certificates.updatedAt,
|
updatedAt: certificates.updatedAt,
|
||||||
errorMessage: certificates.errorMessage,
|
errorMessage: certificates.errorMessage,
|
||||||
renewalCount: certificates.renewalCount
|
renewalCount: certificates.renewalCount,
|
||||||
|
domainType: domains.type
|
||||||
})
|
})
|
||||||
.from(certificates)
|
.from(certificates)
|
||||||
|
.innerJoin(domains, eq(certificates.domainId, domains.domainId))
|
||||||
|
.innerJoin(
|
||||||
|
orgDomains,
|
||||||
|
and(
|
||||||
|
eq(domains.domainId, orgDomains.domainId),
|
||||||
|
eq(orgDomains.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
or(
|
and(
|
||||||
eq(certificates.domain, domain),
|
eq(certificates.wildcard, true),
|
||||||
and(
|
or(
|
||||||
eq(certificates.wildcard, true),
|
inArray(certificates.domain, [...domainLevelDownSet]),
|
||||||
or(
|
inArray(certificates.domain, [...wildcardDomainSet])
|
||||||
eq(certificates.domain, domainLevelDown),
|
|
||||||
eq(certificates.domain, wildcardPrefixed)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
existingCertificates.push(...wildcardCertificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
const certificateMap: Record<string, any> = {};
|
||||||
|
for (const domain of domainList) {
|
||||||
|
const domainLevelDown = domain.split(".").slice(1).join(".");
|
||||||
|
const wildcardPrefixed = `*.${domainLevelDown}`;
|
||||||
|
certificateMap[domain] =
|
||||||
|
existingCertificates.find(
|
||||||
|
(cert) =>
|
||||||
|
cert.domain === domain ||
|
||||||
|
cert.domain === domainLevelDown ||
|
||||||
|
cert.domain === wildcardPrefixed
|
||||||
|
) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBatchedCertificate(
|
export async function getBatchedCertificates(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = getCertificateSchema.safeParse(req.params);
|
const parsedParams = getCertificateParamSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -200,11 +235,22 @@ export async function getBatchedCertificate(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { domains } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
const parsedQuery = getCertificateQuerySchema.safeParse(req.query);
|
||||||
|
if (!parsedQuery.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedQuery.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const cert = await query(domains);
|
const { domains } = parsedQuery.data;
|
||||||
|
|
||||||
return response<GetCertificateResponse>(res, {
|
const cert = await query(orgId, domains);
|
||||||
|
|
||||||
|
return response<GetBatchedCertificateResponse>(res, {
|
||||||
data: cert,
|
data: cert,
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
|
|||||||
@@ -14,3 +14,4 @@
|
|||||||
export * from "./getCertificate";
|
export * from "./getCertificate";
|
||||||
export * from "./restartCertificate";
|
export * from "./restartCertificate";
|
||||||
export * from "./syncCertToNewts";
|
export * from "./syncCertToNewts";
|
||||||
|
export * from "./getBatchedCertificates";
|
||||||
|
|||||||
@@ -175,6 +175,13 @@ authenticated.get(
|
|||||||
certificates.getCertificate
|
certificates.getCertificate
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/batched-certificates",
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.getCertificate),
|
||||||
|
certificates.getBatchedCertificates
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/certificate/:certId/restart",
|
"/org/:orgId/certificate/:certId/restart",
|
||||||
verifyValidLicense,
|
verifyValidLicense,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import type { Domain } from "@server/db";
|
||||||
|
|
||||||
export type GetCertificateResponse = {
|
export type GetCertificateResponse = {
|
||||||
certId: number;
|
certId: number;
|
||||||
domain: string;
|
domain: string;
|
||||||
domainId: string;
|
domainId: string;
|
||||||
wildcard: boolean;
|
wildcard: boolean;
|
||||||
domainType: string;
|
domainType: Domain["type"];
|
||||||
status: string; // pending, requested, valid, expired, failed
|
status: string; // pending, requested, valid, expired, failed
|
||||||
expiresAt: string | null;
|
expiresAt: string | null;
|
||||||
lastRenewalAttempt: Date | null;
|
lastRenewalAttempt: Date | null;
|
||||||
@@ -12,3 +14,8 @@ export type GetCertificateResponse = {
|
|||||||
errorMessage?: string | null;
|
errorMessage?: string | null;
|
||||||
renewalCount: number;
|
renewalCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetBatchedCertificateResponse = Record<
|
||||||
|
string,
|
||||||
|
GetCertificateResponse | null
|
||||||
|
>;
|
||||||
|
|||||||
Reference in New Issue
Block a user