mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-06 07:16:00 -06:00
feat: add entity lookup support and administrative/reserved status types
Implements RDAP entity lookups with service provider tag resolution and adds support for two additional IANA-defined status types (administrative and reserved) to improve RDAP compliance and coverage.
This commit is contained in:
@@ -39,6 +39,8 @@ export const rdapStatusColors: Record<RdapStatusType, BadgeColor> = {
|
|||||||
"redemption period": "orange",
|
"redemption period": "orange",
|
||||||
"renew period": "blue",
|
"renew period": "blue",
|
||||||
"transfer period": "blue",
|
"transfer period": "blue",
|
||||||
|
administrative: "purple",
|
||||||
|
reserved: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rdapStatusInfo: Record<RdapStatusType, string> = {
|
export const rdapStatusInfo: Record<RdapStatusType, string> = {
|
||||||
@@ -104,6 +106,9 @@ export const rdapStatusInfo: Record<RdapStatusType, string> = {
|
|||||||
"The server set the status so that DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverHold' status.",
|
"The server set the status so that DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverHold' status.",
|
||||||
"transfer period":
|
"transfer period":
|
||||||
"This grace period is provided after the successful transfer of object registration sponsorship from one client to another client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the transfer. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'transferPeriod' status.",
|
"This grace period is provided after the successful transfer of object registration sponsorship from one client to another client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the transfer. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'transferPeriod' status.",
|
||||||
|
administrative:
|
||||||
|
"The object instance has been allocated administratively (i.e., not for use by the recipient in their own right in operational networks).",
|
||||||
|
reserved: "The object instance has been allocated to an IANA special-purpose address registry.",
|
||||||
};
|
};
|
||||||
|
|
||||||
// list of RDAP bootstrap registry URLs
|
// list of RDAP bootstrap registry URLs
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const StatusEnum = z.enum([
|
|||||||
"server update prohibited",
|
"server update prohibited",
|
||||||
"server hold",
|
"server hold",
|
||||||
"transfer period",
|
"transfer period",
|
||||||
|
"administrative",
|
||||||
|
"reserved",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const LinkSchema = z.object({
|
export const LinkSchema = z.object({
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import type { AutonomousNumber, Domain, IpNetwork, TargetType } from "@/rdap/schemas";
|
import type { AutonomousNumber, Domain, Entity, IpNetwork, TargetType } from "@/rdap/schemas";
|
||||||
import { AutonomousNumberSchema, DomainSchema, IpNetworkSchema } from "@/rdap/schemas";
|
import {
|
||||||
|
AutonomousNumberSchema,
|
||||||
|
DomainSchema,
|
||||||
|
EntitySchema,
|
||||||
|
IpNetworkSchema,
|
||||||
|
} from "@/rdap/schemas";
|
||||||
import { Result } from "true-myth";
|
import { Result } from "true-myth";
|
||||||
import { loadBootstrap } from "@/rdap/services/registry";
|
import { loadBootstrap } from "@/rdap/services/registry";
|
||||||
import { getRegistryURL } from "@/rdap/services/url-resolver";
|
import { getRegistryURL } from "@/rdap/services/url-resolver";
|
||||||
@@ -7,7 +12,7 @@ import { getAndParse } from "@/rdap/services/rdap-api";
|
|||||||
import type { ParsedGeneric } from "@/rdap/components/Generic";
|
import type { ParsedGeneric } from "@/rdap/components/Generic";
|
||||||
|
|
||||||
// An array of schemas to try and parse unknown JSON data with.
|
// An array of schemas to try and parse unknown JSON data with.
|
||||||
const schemas = [DomainSchema, AutonomousNumberSchema, IpNetworkSchema];
|
const schemas = [DomainSchema, AutonomousNumberSchema, IpNetworkSchema, EntitySchema];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom error for HTTP security warnings that includes the URL for repeatability.
|
* Custom error for HTTP security warnings that includes the URL for repeatability.
|
||||||
@@ -157,9 +162,20 @@ export async function executeRdapQuery(
|
|||||||
}
|
}
|
||||||
return Result.err(new Error("No schema was able to parse the JSON."));
|
return Result.err(new Error("No schema was able to parse the JSON."));
|
||||||
}
|
}
|
||||||
case "entity":
|
case "entity": {
|
||||||
|
await loadBootstrap("entity");
|
||||||
|
const url = getRegistryURL(targetType, target, queryParams);
|
||||||
|
const result = await getAndParse<Entity>(url, EntitySchema, followReferral);
|
||||||
|
if (result.isErr) return Result.err(result.error);
|
||||||
|
return Result.ok({ data: result.value, url });
|
||||||
|
}
|
||||||
case "registrar":
|
case "registrar":
|
||||||
return Result.err(new Error("The type detected has not been implemented."));
|
return Result.err(
|
||||||
|
new Error(
|
||||||
|
"Registrar lookups are not supported as a separate type. " +
|
||||||
|
"In RDAP, registrars are entity objects. Please use the entity type with the registrar's handle (e.g., IANA ID format)."
|
||||||
|
)
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return Result.err(new Error("The type detected has not been implemented."));
|
return Result.err(new Error("The type detected has not been implemented."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,38 @@ export function getRegistryURL(
|
|||||||
}
|
}
|
||||||
throw new Error(`No matching registry found for ${lookupTarget}.`);
|
throw new Error(`No matching registry found for ${lookupTarget}.`);
|
||||||
}
|
}
|
||||||
case "entity":
|
case "entity": {
|
||||||
throw new Error(`No matching entity found.`);
|
// Extract service provider tag from entity handle (text after last hyphen)
|
||||||
|
// Example: "OPS4-RIPE" -> tag is "RIPE"
|
||||||
|
const lastHyphenIndex = lookupTarget.lastIndexOf("-");
|
||||||
|
if (lastHyphenIndex === -1 || lastHyphenIndex === lookupTarget.length - 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid entity handle format: ${lookupTarget}. Expected format: HANDLE-TAG`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceProviderTag = lookupTarget.substring(lastHyphenIndex + 1).toUpperCase();
|
||||||
|
|
||||||
|
// Search for the service provider tag in the bootstrap registry
|
||||||
|
// Entity registry structure: [email, tags, urls]
|
||||||
|
for (const bootstrapItem of bootstrap.services) {
|
||||||
|
const tags = bootstrapItem[1]; // Tags are at index 1 (0=email, 1=tags, 2=urls)
|
||||||
|
const urls = bootstrapItem[2]; // URLs are at index 2
|
||||||
|
|
||||||
|
if (
|
||||||
|
tags.some((tag) => tag.toUpperCase() === serviceProviderTag) &&
|
||||||
|
urls &&
|
||||||
|
urls.length > 0
|
||||||
|
) {
|
||||||
|
url = getBestURL(urls as [string, ...string[]]);
|
||||||
|
break typeSwitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`No matching registry found for entity service provider tag: ${serviceProviderTag}`
|
||||||
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid lookup target provided.");
|
throw new Error("Invalid lookup target provided.");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user