summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2022-09-07 17:22:51 -0700
committerIvan Kohler <ivan@freeside.biz>2022-09-07 17:22:51 -0700
commit26ddb940ad27ce8ac5e87084eeed857a390987bc (patch)
tree3d6e1c46d8d703a1c6bb63820ab741d6a67ab506
parent691de38ac2ca6c54b37feb50e1332bab6268773f (diff)
google authenticator support, RT#86743
-rw-r--r--FS/FS/AuthCookieHandler.pm4
-rw-r--r--FS/FS/Schema.pm1
-rw-r--r--FS/FS/access_user.pm40
-rw-r--r--debian/control2
-rw-r--r--httemplate/browse/access_user.html25
-rw-r--r--httemplate/edit/process/access_user.html2
-rw-r--r--httemplate/loginout/login.html6
-rw-r--r--httemplate/pref/pref.html12
-rw-r--r--httemplate/pref/set_totp_secret32.html19
9 files changed, 102 insertions, 9 deletions
diff --git a/FS/FS/AuthCookieHandler.pm b/FS/FS/AuthCookieHandler.pm
index 93d8ea6..b7d0dbf 100644
--- a/FS/FS/AuthCookieHandler.pm
+++ b/FS/FS/AuthCookieHandler.pm
@@ -13,13 +13,13 @@ sub useragent_ip {
}
sub authen_cred {
- my( $self, $r, $username, $password ) = @_;
+ my( $self, $r, $username, $password, $totp_code ) = @_;
preuser_setup();
my $info = {};
- unless ( FS::Auth->authenticate($username, $password, $info) ) {
+ unless ( FS::Auth->authenticate($username, $password, $totp_code, $info) ) {
warn "failed auth $username from ". $self->useragent_ip($r). "\n";
return undef;
}
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index d884036..61b793b 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -5931,6 +5931,7 @@ sub tables_hashref {
'username', 'varchar', '', $char_d, '', '',
'_password', 'varchar', 'NULL', $char_d, '', '',
'_password_encoding', 'varchar', 'NULL', $char_d, '', '',
+ 'totp_secret32', 'char', 'NULL', 32, '', '',
'last', 'varchar', 'NULL', $char_d, '', '',
'first', 'varchar', 'NULL', $char_d, '', '',
'user_custnum', 'int', 'NULL', '', '', '',
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index f23aa77..270f8bb 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -13,6 +13,7 @@ use FS::agent;
use FS::cust_main;
use FS::sales;
use Carp qw( croak );
+use Auth::GoogleAuth;
$DEBUG = 0;
$me = '[FS::access_user]';
@@ -239,6 +240,7 @@ sub check {
$self->ut_numbern('usernum')
|| $self->ut_alpha_lower('username')
|| $self->ut_textn('_password')
+ || $self->ut_alphan('totp_secret32')
|| $self->ut_textn('last')
|| $self->ut_textn('first')
|| $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
@@ -733,6 +735,44 @@ sub change_password_fields {
FS::Auth->auth_class->change_password_fields( @_ );
}
+=item google_auth
+
+=cut
+
+sub google_auth {
+ my( $self ) = @_;
+ my $issuer = FS::Conf->new->config('company_name'). ' Freeside';
+ my $label = $issuer. ':'. $self->username;
+
+ Auth::GoogleAuth->new({
+ secret => $self->totp_secret32,
+ issuer => $issuer,
+ key_id => $label,
+ });
+
+}
+
+=item set_totp_secret32
+
+=cut
+
+sub set_totp_secret32 {
+ my( $self ) = @_;
+
+ $self->totp_secret32( $self->google_auth->generate_secret32 );
+ $self->replace;
+}
+
+=item totp_qr_code_url
+
+=cut
+
+sub totp_qr_code_url {
+ my( $self ) = @_;
+
+ $self->google_auth->qr_code;
+}
+
=item locale
=cut
diff --git a/debian/control b/debian/control
index 479a150..043294f 100644
--- a/debian/control
+++ b/debian/control
@@ -109,7 +109,7 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,
libspreadsheet-xlsx-perl, libpod-simple-perl, libwebservice-northern911-perl,
liblocale-codes-perl, liblocale-po-perl, libgeo-uscensus-geocoding-perl,
libnet-sftp-foreign-perl, libpdf-webkit-perl, libgeo-shapelib-perl,
- libgeo-json-perl
+ libgeo-json-perl, libauth-googleauth-perl
Conflicts: libparams-classify-perl (>= 0.013-6)
Replaces: freeside (<<4)
Breaks: freeside (<<4)
diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html
index 446bfe0..6587627 100644
--- a/httemplate/browse/access_user.html
+++ b/httemplate/browse/access_user.html
@@ -49,6 +49,11 @@ my $groups_sub = sub {
};
+my $goog_auth_sub = sub {
+ my $access_user = shift;
+ $access_user->totp_secret32 ? 'Enabled' : '';
+};
+
my $installer_sub = sub {
my $access_user = shift;
my @sched_item = $access_user->sched_item or return '';
@@ -66,11 +71,23 @@ my $count_query = 'SELECT COUNT(*) FROM access_user';
my $link = [ $p.'edit/access_user.html?', 'usernum' ];
my @header = (
- 'Username', 'Full name', 'Groups', 'Installer', 'Customer' );
+ 'Username',
+ 'Full name',
+ 'Groups',
+ 'Google Auth',
+ 'Installer',
+ 'Customer',
+);
my @fields = (
- 'username', 'name', $groups_sub, $installer_sub, $cust_sub, );
-my $align = 'lllcl';
-my @links = ( $link, $link, $link, '', '', $cust_link );
+ 'username',
+ 'name',
+ $groups_sub,
+ $goog_auth_sub,
+ $installer_sub,
+ $cust_sub,
+);
+my $align = 'lllccl';
+my @links = ( $link, $link, $link, '', '', '', $cust_link );
#if ( FS::Conf->new->config('ticket_system') ) {
# push @header, 'Ticketing';
diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html
index c272620..8e264c1 100644
--- a/httemplate/edit/process/access_user.html
+++ b/httemplate/edit/process/access_user.html
@@ -5,7 +5,7 @@
<% include( 'elements/process.html',
'table' => 'access_user',
'viewall_dir' => 'browse',
- 'copy_on_empty' => [ '_password', '_password_encoding' ],
+ 'copy_on_empty' => [ '_password', '_password_encoding', 'totp_secret32' ],
'clear_on_error' => [ '_password', '_password2' ],
'process_m2m' => { 'link_table' => 'access_usergroup',
'target_table' => 'access_group',
diff --git a/httemplate/loginout/login.html b/httemplate/loginout/login.html
index 72e9525..1785ea7 100644
--- a/httemplate/loginout/login.html
+++ b/httemplate/loginout/login.html
@@ -27,6 +27,10 @@
<TD ALIGN="right">Password: </TD>
<TD><INPUT TYPE="password" NAME="credential_1" SIZE="13"></TD>
</TR>
+ <TR>
+ <TD ALIGN="right">One-time code: </TD>
+ <TD><INPUT TYPE="text" NAME="credential_2" SIZE="13"></TD>
+ </TR>
</TABLE>
<BR>
@@ -42,7 +46,7 @@
my %error = (
'no_cookie' => '', #First login, don't display an error
'bad_cookie' => 'Bad Cookie', #timed out?
- 'bad_credentials' => 'Incorrect username / password',
+ 'bad_credentials' => 'Incorrect username / password / one-time code',
#'logout' => 'You have been logged out.',
);
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 56fde6d..5f68d3e 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -29,6 +29,18 @@
</TABLE>
<BR>
+ <FONT CLASS="fsinnerbox-title"><% emt('Google Authenticator') %></FONT>
+ <TABLE CLASS="fsinnerbox">
+ <TR>
+% if ( $curuser->totp_secret32 ) {
+ <TD><IMG SRC="<% $curuser->totp_qr_code_url %>"</IMG></TD>
+% } else {
+ <TD><A HREF="<%$p%>pref/set_totp_secret32.html">Enable</A></TD>
+% }
+ </TR>
+ </TABLE>
+ <BR>
+
% }
<FONT CLASS="fsinnerbox-title"><% emt("Interface") %></FONT>
diff --git a/httemplate/pref/set_totp_secret32.html b/httemplate/pref/set_totp_secret32.html
new file mode 100644
index 0000000..f5676bc
--- /dev/null
+++ b/httemplate/pref/set_totp_secret32.html
@@ -0,0 +1,19 @@
+<& /elements/header.html, mt('Google Authenticator for [_1]', $FS::CurrentUser::CurrentUser->username) &>
+
+Scan this code with the Google Authenticator application on your phone.
+<BR><BR>
+
+<IMG SRC="<% $access_user->totp_qr_code_url %>"></IMG>
+<BR><BR>
+
+Future logins will require a 6-digit code generated by the application.
+
+<& /elements/footer.html &>
+<%init>
+
+my $access_user = $FS::CurrentUser::CurrentUser;
+
+my $error = $access_user->set_totp_secret32 unless length($access_user->totp_secret32);
+die $error if $error; #better error handling for this "shouldn't happen" case?
+
+</%init>