@atproto/did
Version:
DID resolution and verification library
116 lines • 4.3 kB
JavaScript
import { z } from 'zod';
import { DidError, InvalidDidError } from './did-error.js';
import { isDidRefAbsolute } from './did-ref.js';
import { canParse } from './lib/uri.js';
import { DID_PLC_PREFIX, DID_WEB_PREFIX, assertDidPlc, assertDidWeb, isDidPlc, isDidWeb, } from './methods.js';
import { matchesIdentifier } from './utils.js';
export const atprotoDidSchema = z
.string()
.refine(isAtprotoDid, `Atproto only allows "plc" and "web" DID methods`);
export function isAtprotoDid(input) {
return isDidPlc(input) || isAtprotoDidWeb(input);
}
export function asAtprotoDid(input) {
assertAtprotoDid(input);
return input;
}
export function assertAtprotoDid(input) {
if (typeof input !== 'string') {
throw new InvalidDidError(typeof input, `DID must be a string`);
}
else if (input.startsWith(DID_PLC_PREFIX)) {
assertDidPlc(input);
}
else if (input.startsWith(DID_WEB_PREFIX)) {
assertAtprotoDidWeb(input);
}
else {
throw new InvalidDidError(input, `Atproto only allows "plc" and "web" DID methods`);
}
}
export function assertAtprotoDidWeb(input) {
assertDidWeb(input);
if (isDidWebWithPath(input)) {
throw new InvalidDidError(input, `Atproto does not allow path components in Web DIDs`);
}
if (isDidWebWithHttpsPort(input)) {
throw new InvalidDidError(input, `Atproto does not allow port numbers in Web DIDs, except for localhost`);
}
}
/**
* @see {@link https://atproto.com/specs/did#blessed-did-methods}
*/
export function isAtprotoDidWeb(input) {
if (!isDidWeb(input)) {
return false;
}
if (isDidWebWithPath(input)) {
return false;
}
if (isDidWebWithHttpsPort(input)) {
return false;
}
return true;
}
function isDidWebWithPath(did) {
return did.includes(':', DID_WEB_PREFIX.length);
}
function isLocalhostDid(did) {
return (did === 'did:web:localhost' ||
did.startsWith('did:web:localhost:') ||
did.startsWith('did:web:localhost%3A'));
}
function isDidWebWithHttpsPort(did) {
if (isLocalhostDid(did))
return false;
const pathIdx = did.indexOf(':', DID_WEB_PREFIX.length);
const hasPort = pathIdx === -1
? // No path component, check if there's a port separator anywhere after
// the "did:web:" prefix
did.includes('%3A', DID_WEB_PREFIX.length)
: // There is a path component; if there is an encoded colon *before* it,
// then there is a port number
did.lastIndexOf('%3A', pathIdx) !== -1;
return hasPort;
}
export function extractAtprotoData(document) {
return {
did: document.id,
aka: document.alsoKnownAs?.find(isAtprotoAka)?.slice(5),
key: document.verificationMethod?.find((isAtprotoVerificationMethod), document),
pds: document.service?.find((isAtprotoPersonalDataServerService), document),
};
}
export function extractPdsUrl(document) {
const service = document.service?.find(isAtprotoPersonalDataServerService, document);
if (!service) {
throw new DidError(document.id, `Document ${document.id} does not contain a (valid) #atproto_pds service URL`, 'did-service-not-found');
}
return new URL(service.serviceEndpoint);
}
export function isAtprotoAka(value) {
return value.startsWith('at://');
}
export function isAtprotoPersonalDataServerService(service) {
return (service?.type === 'AtprotoPersonalDataServer' &&
typeof service.serviceEndpoint === 'string' &&
canParse(service.serviceEndpoint) &&
matchesIdentifier(this.id, 'atproto_pds', service.id));
}
export const ATPROTO_VERIFICATION_METHOD_TYPES = Object.freeze([
'EcdsaSecp256r1VerificationKey2019',
'EcdsaSecp256k1VerificationKey2019',
'Multikey',
]);
export function isAtprotoVerificationMethod(method) {
return (typeof method === 'object' &&
typeof method?.publicKeyMultibase === 'string' &&
ATPROTO_VERIFICATION_METHOD_TYPES.includes(method.type) &&
matchesIdentifier(this.id, 'atproto', method.id));
}
export function isAtprotoDidRefAbsolute(value) {
if (!isDidRefAbsolute(value))
return false;
return isAtprotoDid(value.slice(0, value.indexOf('#')));
}
//# sourceMappingURL=atproto.js.map