Add DKIM record creator

This commit is contained in:
Reimar 2026-01-19 12:41:02 +01:00
parent 7183b21d24
commit 0d41b2b813
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
16 changed files with 206 additions and 22 deletions

View File

@ -10,6 +10,7 @@ export class Field {
categoryName = null; categoryName = null;
isHidden = false; isHidden = false;
isDisabled = false; isDisabled = false;
isRequired = false;
allowMultiple = false; allowMultiple = false;
constructor(key) { constructor(key) {
@ -32,6 +33,11 @@ export class Field {
// Builder methods // Builder methods
required() {
this.isRequired = true;
return this;
}
label(label) { label(label) {
this.displayName = label; this.displayName = label;
return this; return this;

View File

@ -12,8 +12,7 @@ export class FieldInputItem {
appendHtml(parent) { appendHtml(parent) {
const container = document.createElement("div"); const container = document.createElement("div");
container.style.display = "flex"; container.className = "input-container";
container.style.gap = "0.5rem";
if (this.field.allowMultiple) { if (this.field.allowMultiple) {
const wrapper = document.createElement("div"); const wrapper = document.createElement("div");

View File

@ -0,0 +1,55 @@
import { TagListRecord } from "./TagListRecord.js";
import { ConstantTag } from "../tags/ConstantTag.js";
import { EnumTag } from "../tags/EnumTag.js";
import { TextTag } from "../tags/TextTag.js";
import { FlagsTag } from "../tags/FlagsTag.js";
export class DkimRecord extends TagListRecord {
static fields = [
new ConstantTag("v", "DKIM1")
.required()
.hidden()
.pos(0),
new TextTag("p")
.label("Public key")
.desc("Base64-encoded public key data")
.required()
.pos(1),
new FlagsTag("t", ["y", "s"])
.label("Flags")
.desc("Optional extra options that can be enabled")
.options(["Test mode", "Require identical domain in i= and d= tags of DKIM signature (Recommended)"])
.default("")
.pos(1),
new EnumTag("h", ["sha1", "sha256"])
.label("Hash algorithms")
.desc("Which hash algorithms are allowed to be used. If not set, all are allowed")
.options(["SHA-1", "SHA-256"])
.category("advanced")
.pos(1),
new TextTag("n")
.label("Note")
.desc("Any extra comments for humans. Not parsed by machines")
.category("advanced")
.default("")
.pos(1),
new EnumTag("k", ["rsa"])
.disabled()
.default("rsa")
.pos(1),
new EnumTag("s", ["*", "email"])
.disabled()
.default("*")
.pos(1),
];
static categories = {
"advanced": "Advanced",
};
}

View File

@ -78,9 +78,10 @@ export class DmarcRecord extends TagListRecord {
.default(86400) .default(86400)
.pos(2), .pos(2),
new Tag("rf") new EnumTag("rf", ["afrf"])
.disabled() .disabled()
.default("afrf"), // Other values not supported .default("afrf")
.pos(2),
]; ];
static categories = { static categories = {

View File

@ -11,7 +11,7 @@ export class TagListRecord {
static createFromFieldInputItems(items) { static createFromFieldInputItems(items) {
const tokens = items const tokens = items
.filter(item => !item.field.isDisabled && item.isValid()) .filter(item => !item.field.isDisabled && item.isValid())
.filter(item => !item.field.defaultValue || item.getValue() !== item.field.defaultValue) .filter(item => item.field.defaultValue === null || item.getValue() !== item.field.defaultValue)
.map(input => input.toString()); .map(input => input.toString());
const text = tokens.join("; "); const text = tokens.join("; ");

View File

@ -3,7 +3,6 @@ import { Field } from "../Field.js";
export class Term extends Field { export class Term extends Field {
separator = null; separator = null;
isRequired = false;
position = null; position = null;
valueRequirement = ValueRequirement.REQUIRED; valueRequirement = ValueRequirement.REQUIRED;
@ -41,11 +40,6 @@ export class Term extends Field {
// Builder methods // Builder methods
required() {
this.isRequired = true;
return this;
}
pos(i) { pos(i) {
this.position = i; this.position = i;
return this; return this;

View File

@ -17,9 +17,9 @@ export class EnumTag extends Tag {
getInputHtml(id) { getInputHtml(id) {
return `<select id="${id}" name="${this.key}" ${this.isRequired ? "required" : ""}>` + return `<select id="${id}" name="${this.key}" ${this.isRequired ? "required" : ""}>` +
(this.isRequired || this.defaultValue ? "" : `<option value="" selected>&lt;not set&gt;</option>`) + (this.isRequired || this.initialValue ? "" : `<option value="" selected>&lt;not set&gt;</option>`) +
this.values.map((value, i) => this.values.map((value, i) =>
`<option value="${value}" ${this.defaultValue === value ? "selected" : ""}> `<option value="${value}" ${this.initialValue === value ? "selected" : ""}>
${this.optionLabels[i] + (this.defaultValue === value ? " (Default)" : "")} ${this.optionLabels[i] + (this.defaultValue === value ? " (Default)" : "")}
</option>` </option>`
) + ) +

View File

@ -0,0 +1,28 @@
import { Tag } from "./Tag.js";
export class FlagsTag extends Tag {
constructor(key, flags) {
super(key);
this.flags = flags;
}
options(labels) {
this.flagLabels = labels;
return this;
}
getInputHtml() {
return `
<div style="display: flex; flex-direction: column; gap: 0.5rem">` +
this.flags.map((flag, i) => `
<label><input id="${this.id + "-" + flag}" type="checkbox"> ${this.flagLabels[i]}</label>
`).join("") +
"</div>";
}
getInputValue() {
return this.flags.map(flag => document.getElementById(this.id + "-" + flag).checked ? flag : "")
.filter(val => val)
.join(":");
}
}

View File

@ -23,7 +23,7 @@ export class IntTag extends Tag {
} }
getInputHtml(id) { getInputHtml(id) {
return `<input id="${id}" type="number" name="${this.key}" min="${this.min}" max="${this.max}" ${this.isRequired ? "required" : ""} placeholder="${this.defaultValue ?? ""}">`; return `<input id="${id}" type="number" name="${this.key}" min="${this.min}" max="${this.max}" ${this.isRequired ? "required" : ""} placeholder="${this.initialValue ?? ""}">`;
} }
getInputValue(id) { getInputValue(id) {

View File

@ -2,8 +2,8 @@ import { Field } from "../Field.js";
/** A tag within a DMARC/DKIM record */ /** A tag within a DMARC/DKIM record */
export class Tag extends Field { export class Tag extends Field {
isRequired = false;
defaultValue = null; defaultValue = null;
initialValue = null;
position = null; position = null;
constructor(key) { constructor(key) {
@ -34,13 +34,14 @@ export class Tag extends Field {
// Builder methods // Builder methods
required() { default(value) {
this.isRequired = true; this.defaultValue = value;
this.initialValue = value;
return this; return this;
} }
default(value) { initial(value) {
this.defaultValue = value; this.initialValue = value;
return this; return this;
} }

View File

@ -0,0 +1,19 @@
import { Tag } from "./Tag.js";
export class TextTag extends Tag {
constructor(key) {
super(key);
}
validate(value) {
return true;
}
getInputHtml(id) {
return `<input id="${id}" type="text" name="${this.key}" value="${this.initialValue ?? ""}" style="width: 100%">`;
}
getInputValue(id) {
return document.getElementById(id).value;
}
}

View File

@ -1,7 +1,9 @@
import { DkimRecord } from "../records/DkimRecord.js";
import { DmarcRecord } from "../records/DmarcRecord.js"; import { DmarcRecord } from "../records/DmarcRecord.js";
import { SpfRecord } from "../records/SpfRecord.js"; import { SpfRecord } from "../records/SpfRecord.js";
const records = { const records = {
"/dkim-creator": DkimRecord,
"/dmarc-creator": DmarcRecord, "/dmarc-creator": DmarcRecord,
"/spf-creator": SpfRecord, "/spf-creator": SpfRecord,
}; };

View File

@ -31,7 +31,7 @@ h1, h2 {
text-align: center; text-align: center;
} }
label { body > label {
color: #757575; color: #757575;
font-size: 0.8rem; font-size: 0.8rem;
} }
@ -122,7 +122,7 @@ li:not(:first-of-type) {
display: flex; display: flex;
} }
form label { form > label, details > label {
color: black; color: black;
font-weight: bold; font-weight: bold;
display: block; display: block;
@ -134,6 +134,7 @@ form input {
border: 1px solid #BDBDBD; border: 1px solid #BDBDBD;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
transition: border 200ms; transition: border 200ms;
accent-color: black;
} }
form input:focus { form input:focus {
@ -173,6 +174,12 @@ summary:hover {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.input-container {
display: flex;
gap: 0.5rem;
width: 100%;
}
.text-button { .text-button {
background: none; background: none;
border: none; border: none;

57
dkim-creator/index.html Normal file
View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DKIM Record Creator - Generate DKIM DNS Records</title>
<link rel="stylesheet" href="/assets/styles/main.css">
<script type="module" src="/assets/scripts/ui/creator.js"></script>
</head>
<body>
<h1>DKIM Record Creator</h1>
<label for="record">DNS Record</label><br>
<input id="record" type="text" readonly>
<main>
<h2>Create a DKIM DNS Record</h2>
<p>Customize the options below to generate a DKIM record, which will be shown in the input field above.</p>
<form id="form"></form>
<hr>
<p>
This tool can be used to create DKIM DNS records, which together with a signature header can be used
for authorizing emails.
</p>
<p>
When setting up DKIM, you must first generate a public/private key pair. The private key is signed with
the email message and should be automatically included in the DKIM-signature email header, while the
public key should be pasted here in the DKIM DNS record, so that other email servers can verify the
signature.
</p>
<p>
Using DKIM prevents your emails from ending up in the receiver's spam folder. It is recommended to also
set up <a href="/spf-creator">SPF</a> (to authorize certain IP addresses to send mail) and
<a href="/dmarc-creator">DMARC</a> (to specify how to handle failed validation), to minimize the risk of
getting your emails flagged as spam.
</p>
<p>
This tool only helps with setting up the DNS record. The DKIM-signature email header should be
automatically set by your email provider when sending mails.
</p>
<center>
<h3>More tools:</h3>
<a href="/dkim-validator">DKIM Validator Tool</a> &bull;
<a href="/dmarc-creator">DMARC Creator Tool</a> &bull;
<a href="/spf-creator">SPF Creator Tool</a>
</center>
</main>
</body>
</html>

View File

@ -23,7 +23,7 @@
<hr> <hr>
<p> <p>
This tool allows you to create DMARC DNS records, which can be used when you are hosting an email-server This tool allows you to create DMARC DNS records, which can be used when you are hosting an email server
and want to provide an extra layer of security, so other email providers will trust your emails. and want to provide an extra layer of security, so other email providers will trust your emails.
</p> </p>
@ -32,9 +32,16 @@
subdomain (e.g. _dmarc.example.com) with the content being the text above. subdomain (e.g. _dmarc.example.com) with the content being the text above.
</p> </p>
<p>
Using DMARC prevents your emails from ending up in the receiver's spam folder by telling email servers
how strictly to handle validation with <a href="/dkim-creator">DKIM</a> and
<a href="/spf-creator">SPF</a>. It is highly recommended to set up both of these before using DMARC.
</p>
<center> <center>
<h3>More tools:</h3> <h3>More tools:</h3>
<a href="/dmarc-validator">DMARC Validator Tool</a> &bull; <a href="/dmarc-validator">DMARC Validator Tool</a> &bull;
<a href="/DKIM-creator">DKIM Creator Tool</a> &bull;
<a href="/spf-creator">SPF Creator Tool</a> <a href="/spf-creator">SPF Creator Tool</a>
</center> </center>
</main> </main>

View File

@ -49,9 +49,17 @@
current IP address. See the <a href="/spf-macro-guide">Macro Guide</a> for a list of all macros. current IP address. See the <a href="/spf-macro-guide">Macro Guide</a> for a list of all macros.
</p> </p>
<p>
Using SPF prevents your emails from ending up in the receiver's spam folder. It is recommended to also
set up <a href="/dkim-creator">DKIM</a> (to authorize emails by signing them cryptographically) and
<a href="/dmarc-creator">DMARC</a> (to specify how to handle failed validation), to minimize the risk of
getting your emails flagged as spam.
</p>
<center> <center>
<h3>More tools:</h3> <h3>More tools:</h3>
<a href="/spf-validator">SPF Validator Tool</a> &bull; <a href="/spf-validator">SPF Validator Tool</a> &bull;
<a href="/dkim-creator">DKIM Creator Tool</a> &bull;
<a href="/dmarc-creator">DMARC Creator Tool</a> <a href="/dmarc-creator">DMARC Creator Tool</a>
</center> </center>
</main> </main>