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) {
|
||||
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 { Modifier } from "../spf/Modifier.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 {
|
||||
static fields = [
|
||||
new Modifier("v")
|
||||
new VersionTerm("v", "spf1")
|
||||
.required()
|
||||
.validate((key, val) => {
|
||||
if (val !== "spf1") throw new ValidationError(`Version must be "spf1"`);
|
||||
return true;
|
||||
})
|
||||
.pos(0),
|
||||
|
||||
new Mechanism("include")
|
||||
.validate(this.validateDomain)
|
||||
new DomainMechanism("include")
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new Mechanism("a")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(this.validateDomain)
|
||||
new DomainMechanism("a")
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new Mechanism("mx")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(this.validateDomain)
|
||||
new DomainMechanism("mx")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ptr")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(() => { throw new ValidationError(`"ptr" mechanism should not be used`) })
|
||||
new DomainMechanism("ptr")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ipv4")
|
||||
.validate(this.validateIPv4)
|
||||
new IPv4Mechanism("ipv4")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ipv6")
|
||||
.validate(this.validateIPv6)
|
||||
new IPv6Mechanism("ipv6")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("exists")
|
||||
.validate(this.validateDomain)
|
||||
new DomainMechanism("exists")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
@ -56,11 +46,9 @@ export class SpfRecord {
|
||||
.pos(3),
|
||||
|
||||
new Modifier("redirect")
|
||||
.validate(this.validateDomain)
|
||||
.pos(4),
|
||||
|
||||
new Modifier("exp")
|
||||
.validate(this.validateDomain)
|
||||
.pos(4),
|
||||
];
|
||||
|
||||
@ -128,40 +116,9 @@ export class SpfRecord {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
throw new Error("Unimplemented");
|
||||
return this.text
|
||||
.replace(/;\s*$/, "")
|
||||
.split(/;\s*/);
|
||||
}
|
||||
|
||||
getKeyValues() {
|
||||
@ -59,4 +61,12 @@ export class TagListRecord {
|
||||
|
||||
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 { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class Modifier extends Term {
|
||||
separator = "=";
|
||||
@ -6,4 +7,9 @@ export class Modifier extends Term {
|
||||
constructor(key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
validate() {
|
||||
// TODO validate
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { ValueRequirement } from "./ValueRequirement.js";
|
||||
import { Field } from "../Field.js";
|
||||
|
||||
export class Term {
|
||||
export class Term extends Field {
|
||||
separator = null;
|
||||
isRequired = false;
|
||||
position = null;
|
||||
allowMultiple = false;
|
||||
valueRequirement = ValueRequirement.REQUIRED;
|
||||
validationFunction = null;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
super(key)
|
||||
}
|
||||
|
||||
// Builder methods
|
||||
@ -33,9 +33,4 @@ export class Term {
|
||||
this.valueRequirement = requirement;
|
||||
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;
|
||||
defaultValue = null;
|
||||
displayName = null;
|
||||
description = null;
|
||||
categoryName = null;
|
||||
position = null;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
super(key);
|
||||
this.id = "tag-" + key;
|
||||
}
|
||||
|
||||
@ -17,14 +18,6 @@ export class Tag {
|
||||
return true;
|
||||
}
|
||||
|
||||
getInputHtml() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Builder methods
|
||||
|
||||
required() {
|
||||
@ -37,16 +30,6 @@ export class Tag {
|
||||
return this;
|
||||
}
|
||||
|
||||
label(label) {
|
||||
this.displayName = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
desc(description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
category(category) {
|
||||
this.categoryName = category;
|
||||
return this;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user