TOTP self registration in progress (#1359)

This commit is contained in:
Xavier Guimard 2018-02-20 18:36:34 +01:00
parent d8dbf1a615
commit 560341ea51
6 changed files with 215 additions and 12 deletions

View File

@ -1,5 +1,8 @@
package Lemonldap::NG::Common::TOTP;
# This module is inspired by Auth::GoogleAuth written by Gryphon Shafer
# <gryphon@cpan.org>
use strict;
use Mouse;
use Convert::Base32 'decode_base32';
@ -7,6 +10,7 @@ use Digest::HMAC_SHA1 'hmac_sha1_hex';
our $VERSION = '2.0.0';
# Verify that TOTP $code match with $secret
sub verifyCode {
my ( $self, $interval, $range, $secret, $code ) = @_;
my $s = eval { decode_base32($secret) };
@ -22,6 +26,7 @@ sub verifyCode {
return 0;
}
# Internal subroutine that calculates TOTP code using $secret and $interval
sub _code {
my ( $self, $secret, $r, $interval ) = @_;
my $hmac = hmac_sha1_hex(
@ -39,4 +44,11 @@ sub _code {
);
}
# Simply generate new base32 secret
sub newSecret {
my ($self) = @_;
my @chars = ( 'a' .. 'z', 2 .. 7 );
return join( '', @chars[ map { int( rand(32) ) } 1 .. 16 ] );
}
1;

View File

@ -6,7 +6,7 @@ use Mouse;
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Common::Module', 'Lemonldap::NG::Common::TOTP';
extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Common::TOTP';
# INITIALIZATION
@ -23,14 +23,11 @@ has ott => (
sub init {
my ($self) = @_;
return 0 unless $self->SUPER::init;
if ( $self->conf->{totpSelfRegister} ) {
$self->addAuthRoute(
totpregister => { ':action' => 'selfRegister' },
['POST']
);
$self->addAuthRoute( 'totpregister.html' => undef, ['GET'] );
}
$self->addAuthRoute(
totpregister => { ':action' => 'selfRegister' },
['POST']
);
$self->addAuthRoute( 'totpregister.html' => undef, ['GET'] );
return 1;
}
@ -40,7 +37,7 @@ sub selfRegister {
my $user = $req->userData->{ $self->conf->{whatToTrace} };
unless ($user) {
return $self->p->sendError( $req,
'No ' . $self->conf->{whatToTrace} . ' found in user datas', 500 );
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
}
# Verification that user has a valid TOTP app
@ -89,15 +86,48 @@ sub selfRegister {
}
$self->logger->debug('TOTP code verified');
# Now code is verified, let's store the master key in persistent datas
# Now code is verified, let's store the master key in persistent data
$self->p->updatePersistentSession( $req,
{ _totp2fSecret => $token->{_totp2fSecret} } );
$self->userLogger->logger('TOTP registration succeed');
return [ 200, [ 'Content-Type' => 'application/json' ],
['{"result":1}'] ];
}
# Get or generate master key
elsif($action eq 'getkey') {
elsif ( $action eq 'getkey' ) {
my $nk = 0;
my $secret;
if ( $req->param('newkey') or not $req->userData->{_totp2fSecret} ) {
$secret = $self->newSecret;
$nk = 1;
}
else {
$secret = $req->userData->{_totp2fSecret};
}
# Secret is stored in a token: we choose to not accept secret returned
# by Ajax request to avoid some attacks
my $token = $self->ott->createToken(
{
_totp2fSecret => $secret,
}
);
my $portal = $self->conf->{portal};
$portal =~ s#^https?://([^/:]+).*$#$1#;
# QR-code will be generated by a javascript, here we just send data
return $self->p->sendJSONresponse(
$req,
{
secret => $secret,
token => $token,
portal => $portal,
user => $user,
newkey => $nk,
}
);
}
}

View File

@ -0,0 +1,49 @@
###
LemonLDAP::NG TOTP registration script
###
setMsg = (msg, level) ->
$('#msg').html window.translate msg
$('#color').removeClass 'message-positive message-warning alert-success alert-warning'
$('#color').addClass "message-#{level}"
level = 'success' if level == 'positive'
$('#color').addClass "alert-#{level}"
displayError = (j, status, err) ->
console.log 'Error', err
res = JSON.parse j.responseText
if res and res.error
res = res.error.replace /.* /, ''
console.log 'Returned error', res
setMsg res, 'warning'
token=''
getKey = (reset) ->
$.ajax
type: "POST",
url: "#{portal}/totpregister/getkey"
data:
newkey: reset
error: displayError
# Display key and QR code
success: (data) ->
console.log data
# Generate OTP url
s = "otpauth://totp/#{escape(data.portal)}:#{escape(data.user)}?secret=#{data.secret}&issuer=#{escape(data.portal)}"
# Generate QR code
qr = new QRious
element: document.getElementById('qr'),
value: s
size:150
# Display serialized key
$('#serialized').text(s)
# Show message (warning level if key is new)
if data.newkey
setMsg 'yourNewTotpKey', 'warning'
else
setMsg 'yourTotpKey', 'info'
token = data.token
$(document).ready ->
getKey(0)

View File

@ -0,0 +1,65 @@
// Generated by CoffeeScript 1.10.0
/*
LemonLDAP::NG TOTP registration script
*/
(function() {
var displayError, getKey, setMsg, token;
setMsg = function(msg, level) {
$('#msg').html(window.translate(msg));
$('#color').removeClass('message-positive message-warning alert-success alert-warning');
$('#color').addClass("message-" + level);
if (level === 'positive') {
level = 'success';
}
return $('#color').addClass("alert-" + level);
};
displayError = function(j, status, err) {
var res;
console.log('Error', err);
res = JSON.parse(j.responseText);
if (res && res.error) {
res = res.error.replace(/.* /, '');
console.log('Returned error', res);
return setMsg(res, 'warning');
}
};
token = '';
getKey = function(reset) {
return $.ajax({
type: "POST",
url: portal + "/totpregister/getkey",
data: {
newkey: reset
},
error: displayError,
success: function(data) {
var qr, s;
console.log(data);
s = "otpauth://totp/" + (escape(data.portal)) + ":" + (escape(data.user)) + "?secret=" + data.secret + "&issuer=" + (escape(data.portal));
qr = new QRious({
element: document.getElementById('qr'),
value: s,
size: 150
});
$('#serialized').text(s);
if (data.newkey) {
setMsg('yourNewTotpKey', 'warning');
} else {
setMsg('yourTotpKey', 'info');
}
return token = data.token;
}
});
};
$(document).ready(function() {
return getKey(0);
});
}).call(this);

View File

@ -0,0 +1 @@
(function(){var a,b,d,c;d=function(e,f){$("#msg").html(window.translate(e));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+f);if(f==="positive"){f="success"}return $("#color").addClass("alert-"+f)};a=function(f,e,h){var g;console.log("Error",h);g=JSON.parse(f.responseText);if(g&&g.error){g=g.error.replace(/.* /,"");console.log("Returned error",g);return d(g,"warning")}};c="";b=function(e){return $.ajax({type:"POST",url:portal+"/totpregister/getkey",data:{newkey:e},error:a,success:function(h){var f,g;console.log(h);g="otpauth://totp/"+(escape(h.portal))+":"+(escape(h.user))+"?secret="+h.secret+"&issuer="+(escape(h.portal));f=new QRious({element:document.getElementById("qr"),value:g,size:150});$("#serialized").text(g);if(h.newkey){d("yourNewTotpKey","warning")}else{d("yourTotpKey","info")}return c=h.token}})};$(document).ready(function(){return b(0)})}).call(this);

View File

@ -0,0 +1,46 @@
<TMPL_INCLUDE NAME="header.tpl">
<div class="container">
<div id="color" class="message message-positive alert"><span id="msg"></span></div>
</div>
<main id="menucontent" class="container">
<div class="panel panel-info">
<div class="panel-body">
<div>
<canvas id="qr"></canvas>
<pre id="serialized">
</pre>
</div>
<div class="form-group">
<input name="code" type="number" />
</div>
<div class="buttons">
<span id="changekey" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-check"></span>&nbsp;
<span trspan="changeKey">Change key</span>
</span>
<span id="verify" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-check"></span>&nbsp;
<span trspan="verify">Verify</span>
</span>
</div>
</div>
</div>
</main>
<div class="buttons">
<a id="goback" href="" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
<!-- //if:jsminified
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">bwr/qrious/dist/qrious.min.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.min.js"></script>
//else -->
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">bwr/qrious/dist/qrious.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.js"></script>
<!-- //endif -->
<TMPL_INCLUDE NAME="footer.tpl">