Extract mechanisms, create common field class
This commit is contained in:
parent
80ad069793
commit
00c8a999e7
34
assets/scripts/Field.js
Normal file
34
assets/scripts/Field.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Defines any key-value pair in any type of DNS-record
|
||||||
|
* Used for input fields on DNS creator pages
|
||||||
|
*/
|
||||||
|
export class Field {
|
||||||
|
displayName = null;
|
||||||
|
description = null;
|
||||||
|
|
||||||
|
constructor(key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Virtual methods
|
||||||
|
|
||||||
|
getInputHtml() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputValue() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder methods
|
||||||
|
|
||||||
|
label(label) {
|
||||||
|
this.displayName = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
desc(description) {
|
||||||
|
this.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -88,18 +88,4 @@ export class DmarcRecord extends TagListRecord {
|
|||||||
constructor(text) {
|
constructor(text) {
|
||||||
super(text);
|
super(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenize() {
|
|
||||||
return this.text
|
|
||||||
.replace(/;\s*$/, "")
|
|
||||||
.split(/;\s*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldsToString() {
|
|
||||||
let tokens = this.constructor.fields
|
|
||||||
.filter(field => !field.defaultValue || field.getInputValue() !== field.defaultValue)
|
|
||||||
.filter(field => field.getInputValue())
|
|
||||||
.map(field => field.key + "=" + field.getInputValue());
|
|
||||||
return tokens.join("; ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,53 +1,43 @@
|
|||||||
import { Mechanism } from "../spf/Mechanism.js";
|
|
||||||
import { ValidationError } from "../ValidationError.js";
|
import { ValidationError } from "../ValidationError.js";
|
||||||
import { Modifier } from "../spf/Modifier.js";
|
import { Modifier } from "../spf/Modifier.js";
|
||||||
import { ValueRequirement } from "../spf/ValueRequirement.js";
|
import { ValueRequirement } from "../spf/ValueRequirement.js";
|
||||||
|
import { DomainMechanism } from "../spf/DomainMechanism.js";
|
||||||
|
import { IPv4Mechanism } from "../spf/IPv4Mechanism.js";
|
||||||
|
import { IPv6Mechanism } from "../spf/IPv6Mechanism.js";
|
||||||
|
import { Mechanism } from "../spf/Mechanism.js";
|
||||||
|
import { VersionTerm } from "../spf/VersionTerm.js";
|
||||||
|
|
||||||
export class SpfRecord {
|
export class SpfRecord {
|
||||||
static fields = [
|
static fields = [
|
||||||
new Modifier("v")
|
new VersionTerm("v", "spf1")
|
||||||
.required()
|
.required()
|
||||||
.validate((key, val) => {
|
|
||||||
if (val !== "spf1") throw new ValidationError(`Version must be "spf1"`);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.pos(0),
|
.pos(0),
|
||||||
|
|
||||||
new Mechanism("include")
|
new DomainMechanism("include")
|
||||||
.validate(this.validateDomain)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(1),
|
.pos(1),
|
||||||
|
|
||||||
new Mechanism("a")
|
new DomainMechanism("a")
|
||||||
.value(ValueRequirement.OPTIONAL)
|
|
||||||
.validate(this.validateDomain)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(1),
|
.pos(1),
|
||||||
|
|
||||||
new Mechanism("mx")
|
new DomainMechanism("mx")
|
||||||
.value(ValueRequirement.OPTIONAL)
|
|
||||||
.validate(this.validateDomain)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(2),
|
.pos(2),
|
||||||
|
|
||||||
new Mechanism("ptr")
|
new DomainMechanism("ptr")
|
||||||
.value(ValueRequirement.OPTIONAL)
|
|
||||||
.validate(() => { throw new ValidationError(`"ptr" mechanism should not be used`) })
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(2),
|
.pos(2),
|
||||||
|
|
||||||
new Mechanism("ipv4")
|
new IPv4Mechanism("ipv4")
|
||||||
.validate(this.validateIPv4)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(2),
|
.pos(2),
|
||||||
|
|
||||||
new Mechanism("ipv6")
|
new IPv6Mechanism("ipv6")
|
||||||
.validate(this.validateIPv6)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(2),
|
.pos(2),
|
||||||
|
|
||||||
new Mechanism("exists")
|
new DomainMechanism("exists")
|
||||||
.validate(this.validateDomain)
|
|
||||||
.multiple()
|
.multiple()
|
||||||
.pos(2),
|
.pos(2),
|
||||||
|
|
||||||
@ -56,11 +46,9 @@ export class SpfRecord {
|
|||||||
.pos(3),
|
.pos(3),
|
||||||
|
|
||||||
new Modifier("redirect")
|
new Modifier("redirect")
|
||||||
.validate(this.validateDomain)
|
|
||||||
.pos(4),
|
.pos(4),
|
||||||
|
|
||||||
new Modifier("exp")
|
new Modifier("exp")
|
||||||
.validate(this.validateDomain)
|
|
||||||
.pos(4),
|
.pos(4),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -128,40 +116,9 @@ export class SpfRecord {
|
|||||||
throw new ValidationError(`Term "${term.key}" must not have a value`);
|
throw new ValidationError(`Term "${term.key}" must not have a value`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.value) term.validationFunction(term.key, input.value);
|
if (input.value) term.validate(input.value);
|
||||||
|
|
||||||
lastPos = term.position;
|
lastPos = term.position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static validateDomain(key, value) {
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc7208#section-7.1
|
|
||||||
const valid = value
|
|
||||||
.split(".")
|
|
||||||
.every(segment => segment.match(/^([^%]|%_|%%|%-|%\{[slodiphcrtv]\d*r?[-.+,\/_=]*})+$/));
|
|
||||||
|
|
||||||
if (!valid) throw new ValidationError(`Value for "${key}" is not a valid domain name`);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static validateIPv4(key, value) {
|
|
||||||
const segments = value.split(".");
|
|
||||||
|
|
||||||
const valid = segments.every(segment => {
|
|
||||||
const number = parseInt(segment);
|
|
||||||
|
|
||||||
return !isNaN(number) && number >= 0 && number <= 255;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (segments.length !== 4 || !valid) {
|
|
||||||
throw new ValidationError(`Value for ${key} is not a valid IPv4 address`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static validateIPv6(key, value) {
|
|
||||||
return true; // TODO validate properly
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,9 @@ export class TagListRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokenize() {
|
tokenize() {
|
||||||
throw new Error("Unimplemented");
|
return this.text
|
||||||
|
.replace(/;\s*$/, "")
|
||||||
|
.split(/;\s*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyValues() {
|
getKeyValues() {
|
||||||
@ -59,4 +61,12 @@ export class TagListRecord {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldsToString() {
|
||||||
|
let tokens = this.constructor.fields
|
||||||
|
.filter(field => !field.defaultValue || field.getInputValue() !== field.defaultValue)
|
||||||
|
.filter(field => field.getInputValue())
|
||||||
|
.map(field => field.key + "=" + field.getInputValue());
|
||||||
|
return tokens.join("; ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
assets/scripts/spf/DomainMechanism.js
Normal file
21
assets/scripts/spf/DomainMechanism.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ValidationError } from "../ValidationError.js";
|
||||||
|
import { Mechanism } from "./Mechanism.js";
|
||||||
|
|
||||||
|
export class DomainMechanism extends Mechanism {
|
||||||
|
separator = ":";
|
||||||
|
|
||||||
|
constructor(key) {
|
||||||
|
super(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value) {
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7208#section-7.1
|
||||||
|
const valid = value
|
||||||
|
.split(".")
|
||||||
|
.every(segment => segment.match(/^([^%]|%_|%%|%-|%\{[slodiphcrtv]\d*r?[-.+,\/_=]*})+$/));
|
||||||
|
|
||||||
|
if (!valid) throw new ValidationError(`Value for "${this.key}" is not a valid domain name`);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
assets/scripts/spf/IPv4Mechanism.js
Normal file
26
assets/scripts/spf/IPv4Mechanism.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ValidationError } from "../ValidationError.js";
|
||||||
|
import { Mechanism } from "./Mechanism.js";
|
||||||
|
|
||||||
|
export class IPv4Mechanism extends Mechanism {
|
||||||
|
separator = ":";
|
||||||
|
|
||||||
|
constructor(key) {
|
||||||
|
super(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value) {
|
||||||
|
const segments = value.split(".");
|
||||||
|
|
||||||
|
const valid = segments.every(segment => {
|
||||||
|
const number = parseInt(segment);
|
||||||
|
|
||||||
|
return !isNaN(number) && number >= 0 && number <= 255;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (segments.length !== 4 || !valid) {
|
||||||
|
throw new ValidationError(`Value for ${key} is not a valid IPv4 address`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
assets/scripts/spf/IPv6Mechanism.js
Normal file
12
assets/scripts/spf/IPv6Mechanism.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Mechanism } from "./Mechanism.js";
|
||||||
|
|
||||||
|
export class IPv6Mechanism extends Mechanism {
|
||||||
|
constructor(key) {
|
||||||
|
super(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value) {
|
||||||
|
// TODO validate
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { Term } from "./Term.js";
|
import { Term } from "./Term.js";
|
||||||
|
import { ValidationError } from "../ValidationError.js";
|
||||||
|
|
||||||
export class Modifier extends Term {
|
export class Modifier extends Term {
|
||||||
separator = "=";
|
separator = "=";
|
||||||
@ -6,4 +7,9 @@ export class Modifier extends Term {
|
|||||||
constructor(key) {
|
constructor(key) {
|
||||||
super(key);
|
super(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
// TODO validate
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { ValueRequirement } from "./ValueRequirement.js";
|
import { ValueRequirement } from "./ValueRequirement.js";
|
||||||
|
import { Field } from "../Field.js";
|
||||||
|
|
||||||
export class Term {
|
export class Term extends Field {
|
||||||
separator = null;
|
separator = null;
|
||||||
isRequired = false;
|
isRequired = false;
|
||||||
position = null;
|
position = null;
|
||||||
allowMultiple = false;
|
allowMultiple = false;
|
||||||
valueRequirement = ValueRequirement.REQUIRED;
|
valueRequirement = ValueRequirement.REQUIRED;
|
||||||
validationFunction = null;
|
|
||||||
|
|
||||||
constructor(key) {
|
constructor(key) {
|
||||||
this.key = key;
|
super(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder methods
|
// Builder methods
|
||||||
@ -33,9 +33,4 @@ export class Term {
|
|||||||
this.valueRequirement = requirement;
|
this.valueRequirement = requirement;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(func) {
|
|
||||||
this.validationFunction = func;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
17
assets/scripts/spf/VersionTerm.js
Normal file
17
assets/scripts/spf/VersionTerm.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Term } from "./Term.js";
|
||||||
|
import { ValidationError } from "../ValidationError.js";
|
||||||
|
|
||||||
|
export class VersionTerm extends Term {
|
||||||
|
separator = "=";
|
||||||
|
|
||||||
|
constructor(key, version) {
|
||||||
|
super(key);
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value) {
|
||||||
|
if (value !== this.version) throw new ValidationError(`Version must be "${this.version}"`);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
export class Tag {
|
import { Field } from "../Field.js";
|
||||||
|
|
||||||
|
/** A tag within a DMARC/DKIM record */
|
||||||
|
export class Tag extends Field {
|
||||||
isRequired = false;
|
isRequired = false;
|
||||||
defaultValue = null;
|
defaultValue = null;
|
||||||
displayName = null;
|
|
||||||
description = null;
|
|
||||||
categoryName = null;
|
categoryName = null;
|
||||||
position = null;
|
position = null;
|
||||||
|
|
||||||
constructor(key) {
|
constructor(key) {
|
||||||
this.key = key;
|
super(key);
|
||||||
this.id = "tag-" + key;
|
this.id = "tag-" + key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,14 +18,6 @@ export class Tag {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputHtml() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInputValue() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builder methods
|
// Builder methods
|
||||||
|
|
||||||
required() {
|
required() {
|
||||||
@ -37,16 +30,6 @@ export class Tag {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
label(label) {
|
|
||||||
this.displayName = label;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
desc(description) {
|
|
||||||
this.description = description;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
category(category) {
|
category(category) {
|
||||||
this.categoryName = category;
|
this.categoryName = category;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user