Catalyst-Plugin-OpenIDConnect

 view release on metacpan or  search on metacpan

lib/Catalyst/Plugin/OpenIDConnect/Utils/Store.pm  view on Meta::CPAN


Returns the code data hashref or undef if not found.

=cut

sub get_authorization_code {
    my ( $self, $code ) = @_;

    $self->logger->debug("Retrieving authorization code: $code") if $self->logger;

    my $code_data = $self->codes->{$code};

    return unless $code_data;

    # Check if code is expired
    if ( $code_data->{expires_at} < time() ) {
        $self->logger->warn("Authorization code expired: $code") if $self->logger;
        delete $self->codes->{$code};
        return;
    }

    $self->logger->debug("Authorization code found: $code") if $self->logger;
    return $code_data;
}

=head2 consume_authorization_code($code)

Atomically deletes the authorization code and returns its data.  Uses Perl's
C<delete> which fetches and removes the hash entry in a single operation,
making it race-free within a single process.

Returns the code data hashref on success, or C<undef> if the code does not
exist or has expired.

=cut

sub consume_authorization_code {
    my ( $self, $code ) = @_;

    $self->logger->debug("Consuming authorization code: $code") if $self->logger;

    # delete() is atomic within a single process: it removes and returns the
    # value in one step, preventing two concurrent requests from both
    # succeeding a check-then-delete sequence.
    my $code_data = delete $self->codes->{$code};
    return unless $code_data;

    if ( $code_data->{expires_at} < time() ) {
        $self->logger->warn("Authorization code expired at consume time: $code")
            if $self->logger;
        return;
    }

    $self->logger->debug("Authorization code consumed: $code") if $self->logger;
    return $code_data;
}

=head2 store_refresh_token($jti, $sub, $client_id, $ttl)

Stores a refresh token JTI with the associated subject, client, and a TTL in
seconds.  Called at token-issuance time so that the token endpoint can later
enforce single-use semantics via L</consume_refresh_token>.

=cut

sub store_refresh_token {
    my ( $self, $jti, $sub, $client_id, $ttl ) = @_;
    $self->_refresh_tokens->{$jti} = {
        sub       => $sub,
        client_id => $client_id,
        exp       => time() + $ttl,
    };
    # Secondary index by subject so all tokens can be revoked at logout.
    $self->_rt_by_sub->{$sub}{$jti} = 1;
}

=head2 consume_refresh_token($jti)

Atomically removes the JTI from the store and returns the associated data
hashref, or C<undef> if absent or expired (already used / revoked / TTL
elapsed).

=cut

sub consume_refresh_token {
    my ( $self, $jti ) = @_;
    my $data = delete $self->_refresh_tokens->{$jti};
    return unless $data;
    if ( $data->{exp} < time() ) {
        delete $self->_rt_by_sub->{ $data->{sub} }{$jti};
        return;
    }
    delete $self->_rt_by_sub->{ $data->{sub} }{$jti};
    return $data;
}

=head2 revoke_refresh_tokens_for_user($sub)

Removes all refresh token JTIs for the given subject identifier from the store.
Called at logout time to prevent re-use of stolen tokens.

=cut

sub revoke_refresh_tokens_for_user {
    my ( $self, $sub ) = @_;
    my $jtis = delete $self->_rt_by_sub->{$sub} // {};
    delete $self->_refresh_tokens->{$_} for keys %{$jtis};
}

# Generate a cryptographically secure random string for codes and tokens.
# Uses Bytes::Random::Secure to draw from the OS CSPRNG (e.g. /dev/urandom),
# which is safe even after fork() — important for pre-forking servers.
sub _generate_secure_random {
    # 120 random bytes -> 160 base64url characters; after stripping the
    # non-alphanumeric "-" and "_" characters (roughly 3% of chars) we have
    # well over 128 alphanumeric characters to work with.
    my $bytes   = random_bytes(120);
    my $encoded = encode_base64url($bytes);
    $encoded    =~ s/[^a-zA-Z0-9]//g;
    return substr( $encoded, 0, 128 );
}



( run in 0.554 second using v1.01-cache-2.11-cpan-9581c071862 )