Files
oneuptime/Common/Types/Domain.ts

130 lines
3.7 KiB
TypeScript

import DatabaseProperty from "./Database/DatabaseProperty";
import BadDataException from "./Exception/BadDataException";
import { JSONObject, ObjectType } from "./JSON";
import { FindOperator } from "typeorm/find-options/FindOperator";
import Zod, { ZodSchema } from "../Utils/Schema/Zod";
export default class Domain extends DatabaseProperty {
/*
* Reserved TLDs for testing and documentation (per IANA)
* These domains can never have real DNS records, so they're safe for testing
*/
public static readonly TEST_DOMAIN_SUFFIXES: string[] = [
".example.com",
".example.org",
".example.net",
".test",
];
/**
* Checks if a domain is a test/reserved domain that can skip DNS verification.
* Test domains include .example.com, .example.org, .example.net, and .test TLDs.
* These are reserved by IANA for documentation and testing purposes.
*/
public static isTestDomain(domain: string): boolean {
const domainLower: string = domain.toLowerCase().trim();
return Domain.TEST_DOMAIN_SUFFIXES.some((suffix: string) => {
return domainLower.endsWith(suffix);
});
}
private _domain: string = "";
public get domain(): string {
return this._domain;
}
public set domain(v: string) {
const isValid: boolean = Domain.isValidDomain(v);
if (!isValid) {
throw new BadDataException("Domain " + v + " is not in valid format.");
}
this._domain = v;
}
public static isValidDomain(domain: string): boolean {
/*
* Regex-based domain validation
* - Each label (part between dots) must be 1-63 characters
* - Labels can contain alphanumeric characters and hyphens
* - Labels cannot start or end with a hyphen
* - TLD must be at least 2 characters and contain only letters
* - Total length should not exceed 253 characters
*/
if (!domain || domain.length > 253) {
return false;
}
/*
* Domain validation regex:
* ^ - start of string
* (?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+ - one or more labels followed by dot
* [a-zA-Z]{2,63} - TLD: 2-63 letters only
* $ - end of string
*/
const domainRegex: RegExp =
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
return domainRegex.test(domain);
}
public constructor(domain: string) {
super();
this.domain = domain;
}
public override toString(): string {
return this.domain;
}
protected static override toDatabase(
value: Domain | FindOperator<Domain>,
): string | null {
if (value) {
if (typeof value === "string") {
value = new Domain(value);
}
return value.toString();
}
return null;
}
public override toJSON(): JSONObject {
return {
_type: ObjectType.Domain,
value: (this as Domain).toString(),
};
}
public static override fromJSON(json: JSONObject): Domain {
if (json["_type"] === ObjectType.Domain) {
return new Domain((json["value"] as string) || "");
}
throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
}
protected static override fromDatabase(_value: string): Domain | null {
if (_value) {
return new Domain(_value);
}
return null;
}
public static override getSchema(): ZodSchema {
return Zod.object({
_type: Zod.literal(ObjectType.Domain),
value: Zod.string().openapi({
type: "string",
example: "example.com",
}),
}).openapi({
type: "object",
description: "Domain object",
example: { _type: ObjectType.Domain, value: "example.com" },
});
}
}