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 )