@bluesky-social/syntax
Version:
Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc
120 lines • 4.95 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureValidAtUriRegex = exports.ensureValidAtUri = void 0;
const did_1 = require("./did");
const handle_1 = require("./handle");
const nsid_1 = require("./nsid");
// Human-readable constraints on ATURI:
// - following regular URLs, a 8KByte hard total length limit
// - follows ATURI docs on website
// - all ASCII characters, no whitespace. non-ASCII could be URL-encoded
// - starts "at://"
// - "authority" is a valid DID or a valid handle
// - optionally, follow "authority" with "/" and valid NSID as start of path
// - optionally, if NSID given, follow that with "/" and rkey
// - rkey path component can include URL-encoded ("percent encoded"), or:
// ALPHA / DIGIT / "-" / "." / "_" / "~" / ":" / "@" / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
// [a-zA-Z0-9._~:@!$&'\(\)*+,;=-]
// - rkey must have at least one char
// - regardless of path component, a fragment can follow as "#" and then a JSON pointer (RFC-6901)
const ensureValidAtUri = (uri) => {
// JSON pointer is pretty different from rest of URI, so split that out first
const uriParts = uri.split('#');
if (uriParts.length > 2) {
throw new Error('ATURI can have at most one "#", separating fragment out');
}
const fragmentPart = uriParts[1] || null;
uri = uriParts[0];
// check that all chars are boring ASCII
if (!/^[a-zA-Z0-9._~:@!$&')(*+,;=%/-]*$/.test(uri)) {
throw new Error('Disallowed characters in ATURI (ASCII)');
}
const parts = uri.split('/');
if (parts.length >= 3 && (parts[0] !== 'at:' || parts[1].length !== 0)) {
throw new Error('ATURI must start with "at://"');
}
if (parts.length < 3) {
throw new Error('ATURI requires at least method and authority sections');
}
try {
if (parts[2].startsWith('did:')) {
(0, did_1.ensureValidDid)(parts[2]);
}
else {
(0, handle_1.ensureValidHandle)(parts[2]);
}
}
catch {
throw new Error('ATURI authority must be a valid handle or DID');
}
if (parts.length >= 4) {
if (parts[3].length === 0) {
throw new Error('ATURI can not have a slash after authority without a path segment');
}
try {
(0, nsid_1.ensureValidNsid)(parts[3]);
}
catch {
throw new Error('ATURI requires first path segment (if supplied) to be valid NSID');
}
}
if (parts.length >= 5) {
if (parts[4].length === 0) {
throw new Error('ATURI can not have a slash after collection, unless record key is provided');
}
// would validate rkey here, but there are basically no constraints!
}
if (parts.length >= 6) {
throw new Error('ATURI path can have at most two parts, and no trailing slash');
}
if (uriParts.length >= 2 && fragmentPart == null) {
throw new Error('ATURI fragment must be non-empty and start with slash');
}
if (fragmentPart != null) {
if (fragmentPart.length === 0 || fragmentPart[0] !== '/') {
throw new Error('ATURI fragment must be non-empty and start with slash');
}
// NOTE: enforcing *some* checks here for sanity. Eg, at least no whitespace
if (!/^\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\]/-]*$/.test(fragmentPart)) {
throw new Error('Disallowed characters in ATURI fragment (ASCII)');
}
}
if (uri.length > 8 * 1024) {
throw new Error('ATURI is far too long');
}
};
exports.ensureValidAtUri = ensureValidAtUri;
const ensureValidAtUriRegex = (uri) => {
// simple regex to enforce most constraints via just regex and length.
// hand wrote this regex based on above constraints. whew!
const aturiRegex = /^at:\/\/(?<authority>[a-zA-Z0-9._:%-]+)(\/(?<collection>[a-zA-Z0-9-.]+)(\/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
const rm = uri.match(aturiRegex);
if (!rm || !rm.groups) {
throw new Error("ATURI didn't validate via regex");
}
const groups = rm.groups;
try {
(0, handle_1.ensureValidHandleRegex)(groups.authority);
}
catch {
try {
(0, did_1.ensureValidDidRegex)(groups.authority);
}
catch {
throw new Error('ATURI authority must be a valid handle or DID');
}
}
if (groups.collection) {
try {
(0, nsid_1.ensureValidNsidRegex)(groups.collection);
}
catch {
throw new Error('ATURI collection path segment must be a valid NSID');
}
}
if (uri.length > 8 * 1024) {
throw new Error('ATURI is far too long');
}
};
exports.ensureValidAtUriRegex = ensureValidAtUriRegex;
//# sourceMappingURL=aturi_validation.js.map