add cert resolver

This commit is contained in:
Pallavi Kumari
2025-10-06 21:00:07 +05:30
parent 60379a7b4e
commit 3f3e9cf1bb
5 changed files with 92 additions and 22 deletions

View File

@@ -1891,5 +1891,9 @@
"cannotbeUndone": "This can not be undone.",
"toConfirm": "to confirm",
"deleteClientQuestion": "Are you sure you want to remove the client from the site and organization?",
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site."
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site.",
"certResolver": "Certificate Resolver",
"certResolverDescription": "Select the certificate resolver to use for this resource.",
"selectCertResolver": "Select Certificate Resolver",
"enterCustomResolver": "Enter Custom Resolver"
}

View File

@@ -11,7 +11,9 @@ export const domains = sqliteTable("domains", {
type: text("type"), // "ns", "cname", "wildcard"
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
failed: integer("failed", { mode: "boolean" }).notNull().default(false),
tries: integer("tries").notNull().default(0)
tries: integer("tries").notNull().default(0),
certResolver: text("certResolver").default("letsencrypt"),
customCertResolver: text("customCertResolver")
});
export const orgs = sqliteTable("orgs", {

View File

@@ -24,16 +24,21 @@ const paramsSchema = z
const bodySchema = z
.object({
type: z.enum(["ns", "cname", "wildcard"]),
baseDomain: subdomainSchema
baseDomain: subdomainSchema,
certResolver: z.enum(["letsencrypt", "custom"]).optional(), // optional, only for wildcard
customCertResolver: z.string().optional() // required if certResolver === "custom"
})
.strict();
export type CreateDomainResponse = {
domainId: string;
nsRecords?: string[];
cnameRecords?: { baseDomain: string; value: string }[];
aRecords?: { baseDomain: string; value: string }[];
txtRecords?: { baseDomain: string; value: string }[];
certResolver?: string | null;
customCertResolver?: string | null;
};
// Helper to check if a domain is a subdomain or equal to another domain
@@ -71,7 +76,7 @@ export async function createOrgDomain(
}
const { orgId } = parsedParams.data;
const { type, baseDomain } = parsedBody.data;
const { type, baseDomain, certResolver, customCertResolver } = parsedBody.data;
if (build == "oss") {
if (type !== "wildcard") {
@@ -254,7 +259,9 @@ export async function createOrgDomain(
domainId,
baseDomain,
type,
verified: type === "wildcard" ? true : false
verified: type === "wildcard" ? true : false,
certResolver: certResolver || null,
customCertResolver: customCertResolver || null
})
.returning();
@@ -325,7 +332,9 @@ export async function createOrgDomain(
cnameRecords,
txtRecords,
nsRecords,
aRecords
aRecords,
certResolver: returned.certResolver,
customCertResolver: returned.customCertResolver
},
success: true,
error: false,

View File

@@ -42,7 +42,9 @@ async function queryDomains(orgId: string, limit: number, offset: number) {
type: domains.type,
failed: domains.failed,
tries: domains.tries,
configManaged: domains.configManaged
configManaged: domains.configManaged,
certResolver: domains.certResolver,
customCertResolver: domains.customCertResolver,
})
.from(orgDomains)
.where(eq(orgDomains.orgId, orgId))

View File

@@ -45,6 +45,7 @@ import {
import { useOrgContext } from "@app/hooks/useOrgContext";
import { build } from "@server/build";
import { toASCII, toUnicode } from 'punycode';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
// Helper functions for Unicode domain handling
@@ -96,7 +97,9 @@ const formSchema = z.object({
.min(1, "Domain is required")
.refine((val) => isValidDomainFormat(val), "Invalid domain format")
.transform((val) => toPunycode(val)),
type: z.enum(["ns", "cname", "wildcard"])
type: z.enum(["ns", "cname", "wildcard"]),
certResolver: z.string().optional(),
customCertResolver: z.string().optional()
});
type FormValues = z.infer<typeof formSchema>;
@@ -107,6 +110,12 @@ type CreateDomainFormProps = {
onCreated?: (domain: CreateDomainResponse) => void;
};
// Example cert resolver options (replace with real API/fetch if needed)
const certResolverOptions = [
{ id: "letsencrypt", title: "Let's Encrypt" },
{ id: "custom", title: "Custom Resolver" }
];
export default function CreateDomainForm({
open,
setOpen,
@@ -125,15 +134,26 @@ export default function CreateDomainForm({
resolver: zodResolver(formSchema),
defaultValues: {
baseDomain: "",
type: build == "oss" || !env.flags.usePangolinDns ? "wildcard" : "ns"
type: build == "oss" || !env.flags.usePangolinDns ? "wildcard" : "ns",
certResolver: "",
customCertResolver: ""
}
});
function reset() {
const baseDomain = form.watch("baseDomain");
const domainType = form.watch("type");
const punycodePreview = useMemo(() => {
if (!baseDomain) return "";
const punycode = toPunycode(baseDomain);
return punycode !== baseDomain.toLowerCase() ? punycode : "";
}, [baseDomain]);
const reset = () => {
form.reset();
setLoading(false);
setCreatedDomain(null);
}
};
async function onSubmit(values: FormValues) {
setLoading(true);
@@ -158,17 +178,9 @@ export default function CreateDomainForm({
} finally {
setLoading(false);
}
}
const baseDomain = form.watch("baseDomain");
const domainInputValue = form.watch("baseDomain") || "";
const punycodePreview = useMemo(() => {
if (!domainInputValue) return "";
const punycode = toPunycode(domainInputValue);
return punycode !== domainInputValue.toLowerCase() ? punycode : "";
}, [domainInputValue]);
};
// Domain type options
let domainOptions: any = [];
if (build != "oss" && env.flags.usePangolinDns) {
domainOptions = [
@@ -250,7 +262,7 @@ export default function CreateDomainForm({
<AlertTitle>{t("internationaldomaindetected")}</AlertTitle>
<AlertDescription>
<div className="mt-2 space-y-1">
<p>{t("willbestoredas")} <code className="font-mono px-1 py-0.5 rounded">{punycodePreview}</code></p>
<p>{t("willbestoredas")} <code className="font-mono px-1 py-0.5 rounded">{punycodePreview}</code></p>
</div>
</AlertDescription>
</Alert>
@@ -260,6 +272,47 @@ export default function CreateDomainForm({
</FormItem>
)}
/>
{domainType === "wildcard" && (
<FormField
control={form.control}
name="certResolver"
render={({ field }) => (
<FormItem>
<FormLabel>{t("certResolver")}</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={(val) => field.onChange(val)}
>
<SelectTrigger>
<SelectValue placeholder={t("selectCertResolver")} />
</SelectTrigger>
<SelectContent>
{certResolverOptions.map((opt) => (
<SelectItem key={opt.id} value={opt.id}>
{opt.title}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
{field.value === "custom" && (
<FormControl className="mt-2">
<Input
placeholder={t("enterCustomResolver")}
value={form.watch("customCertResolver") || ""}
onChange={(e) =>
form.setValue("customCertResolver", e.target.value)
}
/>
</FormControl>
)}
</FormItem>
)}
/>
)}
</form>
</Form>
) : (