At

 view release on metacpan or  search on metacpan

lib/At/Protocol/DID.pm  view on Meta::CPAN

package At::Protocol::DID 1.0 {
    use v5.42;
    no warnings qw[experimental::try];
    use feature 'try';
    use At::Error qw[register throw];
    use parent -norequire => 'Exporter';
    use overload
        '""' => sub ( $s, $u, $q ) {
        $$s;
        };
    our %EXPORT_TAGS = ( all => [ our @EXPORT_OK = qw[ensureValidDid ensureValidDidRegex] ] );

    sub new( $class, $did ) {
        try {
            ensureValidDid($did);
        }
        catch ($err) { return; }
        bless \$did, $class;
    }

    #~ Taken from https://github.com/bluesky-social/atproto/blob/main/packages/syntax/src/did.ts
    #~ Human-readable constraints:
    #~   - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)
    #~      - entire URI is ASCII: [a-zA-Z0-9._:%-]
    #~      - always starts "did:" (lower-case)
    #~      - method name is one or more lower-case letters, followed by ":"
    #~      - remaining identifier can have any of the above chars, but can not end in ":"
    #~      - it seems that a bunch of ":" can be included, and don't need spaces between
    #~      - "%" is used only for "percent encoding" and must be followed by two hex characters (and thus can't end in "%")
    #~      - query ("?") and fragment ("#") stuff is defined for "DID URIs", but not as part of identifier itself
    #~      - "The current specification does not take a position on the maximum length of a DID"
    #~   - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer
    #~   - hard length limit of 8KBytes
    #~   - not going to validate "percent encoding" here
    sub ensureValidDid ($did) {

        # check that all chars are boring ASCII
        throw InvalidDidError('Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)')
            unless $did =~ /^[a-zA-Z0-9._:%-]*$/;
        #
        my @parts = split ':', $did, -1;    # negative limit, ftw
        throw InvalidDidError('DID requires prefix, method, and method-specific content') if @parts < 3;
        #
        throw InvalidDidError('DID requires "did:" prefix') if $parts[0] ne 'did';
        #
        throw InvalidDidError('DID method must be lower-case letters') if $parts[1] !~ /^[a-z]+$/;
        #
        throw InvalidDidError('DID can not end with ":" or "%"')       if $did =~ /[:%]$/;
        throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
        1;
    }

    sub ensureValidDidRegex ($did) {

        #~ simple regex to enforce most constraints via just regex and length.
        #~ hand wrote this regex based on above constraints
        throw InvalidDidError(q[DID didn't validate via regex])        if $did !~ /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/;
        throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
        #
        1;
    }

    # fatal error
    register 'InvalidDidError', 1;
};
1;
__END__
=encoding utf-8

=head1 NAME

At::Protocol::DID - AT Protocol DID Validation

=head1 SYNOPSIS

    use At::Protocol::DID qw[:all];
    try {
        ensureValidDid( 'did:method:val' );
    }
    catch($err) {
        ...; # do something about it
    }

=head1 DESCRIPTION

The AT Protocol uses L<Decentralized Identifiers|https://en.wikipedia.org/wiki/Decentralized_identifier> (DIDs) as
persistent, long-term account identifiers. DID is a W3C standard, with many standardized and proposed DID method
implementations.

This package aims to validate them.

=head1 Functions

You may import functions by name or with the C<:all> tag.



( run in 0.479 second using v1.01-cache-2.11-cpan-39bf76dae61 )