From 560341ea51e4cc879db487abcc29ac25d1ea5588 Mon Sep 17 00:00:00 2001 From: Xavier Guimard Date: Tue, 20 Feb 2018 18:36:34 +0100 Subject: [PATCH] TOTP self registration in progress (#1359) --- .../lib/Lemonldap/NG/Common/TOTP.pm | 12 ++++ .../Lemonldap/NG/Portal/2F/Register/TOTP.pm | 54 +++++++++++---- .../site/coffee/totpregistration.coffee | 49 ++++++++++++++ .../static/common/js/totpregistration.js | 65 +++++++++++++++++++ .../static/common/js/totpregistration.min.js | 1 + .../site/templates/bootstrap/totpregister.tpl | 46 +++++++++++++ 6 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 lemonldap-ng-portal/site/coffee/totpregistration.coffee create mode 100644 lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js create mode 100644 lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.min.js create mode 100644 lemonldap-ng-portal/site/templates/bootstrap/totpregister.tpl diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/TOTP.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/TOTP.pm index f3c1f7ab8..a03ca0c38 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/TOTP.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/TOTP.pm @@ -1,5 +1,8 @@ package Lemonldap::NG::Common::TOTP; +# This module is inspired by Auth::GoogleAuth written by Gryphon Shafer +# + 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; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm index 23895a9a9..700c4109e 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm @@ -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, + } + ); } } diff --git a/lemonldap-ng-portal/site/coffee/totpregistration.coffee b/lemonldap-ng-portal/site/coffee/totpregistration.coffee new file mode 100644 index 000000000..f01fc7a2c --- /dev/null +++ b/lemonldap-ng-portal/site/coffee/totpregistration.coffee @@ -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) diff --git a/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js new file mode 100644 index 000000000..af387ea3f --- /dev/null +++ b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js @@ -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); diff --git a/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.min.js b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.min.js new file mode 100644 index 000000000..0d2166b25 --- /dev/null +++ b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.min.js @@ -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); \ No newline at end of file diff --git a/lemonldap-ng-portal/site/templates/bootstrap/totpregister.tpl b/lemonldap-ng-portal/site/templates/bootstrap/totpregister.tpl new file mode 100644 index 000000000..8e00f8b83 --- /dev/null +++ b/lemonldap-ng-portal/site/templates/bootstrap/totpregister.tpl @@ -0,0 +1,46 @@ + + +
+
+
+ +
+
+
+
+ +
+      
+
+
+ +
+
+ +   + Change key + + +   + Verify + +
+
+
+
+ + + + + + + +