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.