Bivio::Type::AccessCode
# Copyright (c) 2025 bivio Software, Inc. All Rights Reserved.
package Bivio::Type::AccessCode;
use strict;
use Bivio::Base 'Type.Enum';
my($_DT) = b_use('Type.DateTime');
my($_MC) = b_use('Type.MnemonicCode');
my($_R) = b_use('Biz.Random');
my($_TE) = b_use('Bivio.TypeError');
__PACKAGE__->compile([
UNKNOWN => 0,
LOGIN_CHALLENGE => 1,
ESCALATION_CHALLENGE => 2,
MFA_RECOVERY => 3,
PASSWORD_QUERY => 4,
]);
my($_CHARS) = ['a'..'z', 'A'..'Z', '0'..'9'];
my($_WIDTH) = 64;
my($_EPHEMERAL_TYPES) = [qw(
LOGIN_CHALLENGE
ESCALATION_CHALLENGE
PASSWORD_QUERY
)];
my($_C) = b_use('IO.Config');
$_C->register(my $_CFG = {
login_challenge_expiry_seconds => 5 * 60,
escalation_challenge_expiry_seconds => 5 * 60,
password_query_expiry_seconds => 60 * 60,
});
sub from_literal_for_type {
my($self, $value) = _assert_specified(@_);
if ($self->eq_mfa_recovery) {
return $_MC->from_literal($value);
}
return (undef, $_TE->SYNTAX_ERROR)
unless $value =~ qr/^[@$_CHARS]+$/;
return $value;
}
sub generate_code_for_type {
my($self) = _assert_specified(@_);
return $_R->string($_WIDTH, $_CHARS)
if $self->equals_by_name(@$_EPHEMERAL_TYPES);
return $_MC->generate_code
if $self->eq_mfa_recovery;
b_die('unsupported type');
# DOES NOT RETURN
}
sub get_expiry_for_type {
my($self) = _assert_specified(@_);
return $_DT->add_seconds($_DT->now, $_CFG->{_expiry_cfg_field($self)})
if $self->equals_by_name(@$_EPHEMERAL_TYPES);
return undef
if $self->eq_mfa_recovery;
b_die('unsupported type');
# DOES NOT RETURN
}
sub get_expiry_seconds_for_type {
my($self) = _assert_specified(@_);
return $_CFG->{_expiry_cfg_field($self)} // 0;
}
sub handle_config {
my(undef, $cfg) = @_;
foreach my $t (@$_EPHEMERAL_TYPES) {
b_die('missing expiry seconds for type=', $t)
unless $cfg->{_expiry_cfg_field($t)};
}
$_CFG = $cfg;
return;
}
sub _assert_specified {
my($self) = @_;
b_die('no type')
unless $self->is_specified;
return @_;
}
sub _expiry_cfg_field {
my($type) = @_;
$type = $type->get_name
if ref($type);
return lc($type) . '_expiry_seconds';
}
1;