NAME
Airlock::Factor::TOTP - Time-based one-time password (RFC 6238) as an Airlock factor
VERSION
version 0.001
SYNOPSIS
my $totp = Airlock::Factor::TOTP->new(
secret => sub { my ( $subject ) = @_; $db->totp_secret( $subject->{id} ) },
last_step => sub { my ( $subject ) = @_; $db->totp_step( $subject->{id} ) },
accept_step => sub { my ( $subject, $step ) = @_; $db->set_totp_step( $subject->{id}, $step ) },
);
# enrolment
my $secret = $totp->generate_secret;
my $uri = $totp->otpauth_uri( secret => $secret, account => 'getty@example.org', issuer => 'Mothership' );
DESCRIPTION
TOTP with HMAC-SHA1, which is what authenticator apps implement. Airlock stores nothing itself: the secret and the last accepted time step come from the host application through three coderefs.
A code is accepted once. last_step and accept_step are what make that true, which is why both are required.
secret
Required. Coderef called with the subject; returns the raw secret bytes, or nothing (or an empty string) when the subject has not enrolled.
last_step
Required. Coderef called with the subject; returns the last accepted time step, or nothing when there is none yet.
accept_step
Required. Coderef called with the subject and the time step that is being accepted. It stores the step, so the same code cannot be used again, and returns true. Where two requests can arrive at once, store only if the new step is greater than the stored one and return false otherwise; the approval then fails instead of accepting one code twice.
digits
Length of a code. Default 6.
period
Seconds per time step. Default 30.
window
Time steps accepted before and after the current one, for clock drift. Default 1.
now
Coderef returning the current epoch. For tests.
code_at
my $code = $totp->code_at( $secret, int( time / 30 ) );
The code for a secret at a time step.
commit
$totp->verify( $subject, $proof ) && $totp->commit( $subject, $proof )
Uses the code up. Airlock calls this after every factor has verified; code that uses this class on its own has to call it after verify.
generate_secret
my $secret = $totp->generate_secret;
Twenty random bytes for a new enrolment.
base32
my $text = $totp->base32($secret);
RFC 4648 base32 without padding, the form authenticator apps take.
otpauth_uri
my $uri = $totp->otpauth_uri( secret => $secret, account => 'getty@example.org', issuer => 'Mothership' );
The otpauth:// URI for enrolment. Feed it to Airlock::QR.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-airlock/issues.
IRC
Join #kubernetes on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.