Full ajax registration (#1148)

This commit is contained in:
Xavier Guimard 2017-02-08 18:10:06 +00:00
parent c54ac7f96b
commit 8768b563fa
12 changed files with 242 additions and 58 deletions

View File

@ -2,6 +2,7 @@ package Lemonldap::NG::Portal::Lib::U2F;
use strict;
use Mouse;
use MIME::Base64 qw(encode_base64 decode_base64);
our $VERSION = '2.0.0';
@ -40,4 +41,20 @@ sub init {
return 1;
}
sub encode_base64url {
shift;
my $e = encode_base64( shift, '' );
$e =~ s/=+\z//;
$e =~ tr[+/][-_];
return $e;
}
sub decode_base64url {
shift;
my $s = shift;
$s =~ tr[-_][+/];
$s .= '=' while length($s) % 4;
return decode_base64($s);
}
1;

View File

@ -6,7 +6,6 @@ package Lemonldap::NG::Portal::Plugins::U2F;
use strict;
use Mouse;
use MIME::Base64;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR
PE_NOTOKEN
@ -148,8 +147,8 @@ sub loadUser {
if ( ( $kh = $req->sessionInfo->{_u2fKeyHandle} )
and ( $uk = $req->sessionInfo->{_u2fUserKey} ) )
{
$self->crypter->{keyHandle} = decode_base64($kh);
$self->crypter->{publicKey} = decode_base64($uk);
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
$self->crypter->{publicKey} = $self->decode_base64url($uk);
unless ( $self->crypter->setKeyHandle and $self->crypter->setPublicKey )
{
$self->lmLog(

View File

@ -3,7 +3,6 @@ package Lemonldap::NG::Portal::Register::U2F;
use strict;
use Mouse;
use MIME::Base64;
our $VERSION = '2.0.0';
@ -42,8 +41,8 @@ sub run {
$self->p->updatePersistentSession(
$req,
{
_u2fKeyHandle => encode_base64( $keyHandle, '' ),
_u2fUserKey => encode_base64( $userKey, '' )
_u2fKeyHandle => $self->encode_base64url( $keyHandle, '' ),
_u2fUserKey => $self->encode_base64url( $userKey, '' )
}
);
return [
@ -90,14 +89,14 @@ sub run {
sub loadUser {
my ( $self, $req ) = @_;
my $uid = $req->userData->{ $self->conf->{whatToTrace} };
my $session = getPersistentSession($uid);
my $session = $self->p->getPersistentSession($uid);
my $kh = $session->data->{_u2fKeyHandle};
my $uk = $session->data->{_u2fUserKey};
unless ( $kh and $uk ) {
return 0;
}
$self->crypter->{keyHandle} = decode_base64($kh);
$self->crypter->{publicKey} = decode_base64($uk);
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
$self->crypter->{publicKey} = $self->decode_base64url($uk);
unless ( $self->crypter->setKeyHandle and $self->crypter->setPublicKey ) {
my $error = Crypt::U2F::Server::Simple::lastError();
return ( -1, $error );

View File

@ -17,6 +17,8 @@ translatePage = (lang) ->
translate = (str) ->
return if translationFields[str] then translationFields[str] else str
window.translate = translate
# Initialization variables
getValues = () ->
values = {}

View File

@ -2,15 +2,89 @@
LemonLDAP::NG U2F 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}"
register = ->
request = [
challenge: window.datas.challenge
version: 'U2F_V2'
]
console.log 'Register: ', request
u2f.register window.datas.appId, request, [], (data) ->
$('#bind-data').val JSON.stringify data
$('#bind-form').submit()
# 1 get registration token
$.ajax
type: "POST",
url: "#{portal}u2fregister/register"
data: {}
dataType: 'json'
error: (j, status, err) ->
console.log 'Error', err
success: (ch) ->
# 2 build response
request = [
challenge: ch.challenge
version: ch.version
]
setMsg 'touchU2fDevice', 'positive'
$('#u2fPermission').show()
u2f.register ch.appId, request, [], (data) ->
$('#u2fPermission').hide()
# Handle errors
if data.errorCode
setMsg 'unableToGetU2FKey', 'warning'
else
# 3 send response
$.ajax
type: "POST"
url: "#{portal}u2fregister/registration"
data:
registration: JSON.stringify data
dataType: 'json'
success: (resp) ->
if resp.error
setMsg 'u2fFailed', 'warning'
else if resp.result
setMsg 'u2fSuccess', 'positive'
error: (j, status, err) ->
console.log 'error', err
verify = ->
# 1 get challenge
$.ajax
type: "POST",
url: "#{portal}u2fregister/verify"
data: {}
dataType: 'json'
error: (j, status, err) ->
console.log 'Error', err
success: (ch) ->
# 2 build response
request = [
keyHandle: ch.keyHandle
version: ch.version
]
setMsg 'touchU2fDevice', 'positive'
u2f.sign ch.appId, ch.challenge, request, (data) ->
# Handle errors
if data.errorCode
setMsg 'unableToGetU2FKey', 'warning'
else
# 3 send response
$.ajax
type: "POST"
url: "#{portal}u2fregister/signature"
data:
signature: JSON.stringify data
dataType: 'json'
success: (resp) ->
if resp.error
setMsg 'u2fFailed', 'warning'
else if resp.result
setMsg 'u2fSuccess', 'positive'
error: (j, status, err) ->
console.log 'error', err
$(document).ready ->
setTimeout register, 1000
$('#u2fPermission').hide()
$('#register').on 'click', register
$('#verify').on 'click', verify
$('#goback').attr 'href', portal

View File

@ -30,6 +30,8 @@ LemonLDAP::NG Portal jQuery scripts
}
};
window.translate = translate;
getValues = function() {
var values;
values = {};

View File

@ -1 +1 @@
(function(){var e,i,d,a,j,h,f,b,k,c,g=[].indexOf||function(o){for(var n=0,m=this.length;n<m;n++){if(n in this&&this[n]===o){return n}}return -1};c={};k=function(l){return $.getJSON(window.staticPrefix+"languages/"+l+".json",function(m){c=m;$("[trspan]").each(function(){return $(this).text(b($(this).attr("trspan")))});return $("[trmsg]").each(function(){return $(this).text(b("PE"+($(this).attr("trmsg"))))})})};b=function(l){if(c[l]){return c[l]}else{return l}};d=function(){var l;l={};$("script[type='application/init']").each(function(){var q,n,m,p,o;try{o=JSON.parse($(this).text());p=[];for(m in o){p.push(l[m]=o[m])}return p}catch(n){q=n;console.log("Parsing error",q);return console.log("JSON",$(this).text())}});return l};f="#appslist";i=function(){return $.ajax({type:"POST",url:e.scriptname,data:{storeAppsListOrder:$(f).sortable("toArray").join()},dataType:"json"})};h=function(){var n,m,q,y,l,u,p,r,o,t,w,s,x;t=$(f);if(!((t!=null)&&e.appslistorder)){return null}n=e.appslistorder.split(",");u=t.sortable("toArray");w=[];for(q=0,r=u.length;q<r;q++){x=u[q];w[x]=x}for(p=0,o=n.length;p<o;p++){l=n[p];if(g.call(w,l)>=0){y=w[l];m=$(f+".ui-sortable").children("#"+y);s=$(f+".ui-sortable").children("#"+l);m.remove();$(f+".ui-sortable").filter(":first").append(s)}}return 1};a=function(l){return $("#lmhidden_"+l).length};j=function(){return $.ajax({type:"POST",url:e.scriptname,data:{ping:1},dataType:"json",success:function(l){if(l.auth){return setTimeout(j,e.pingInterval)}else{return location.reload(true)}}})};window.ping=j;e={};$(document).ready(function(){var x,w,z,v,A,y,C,t,s,B,r,q,u,o,m,n,l,p;e=d();window.datas=e;if(e.antiframe&&top!==self){top.location.href=location.href}$("#appslist").sortable({axis:"y",cursor:"move",opacity:0.5,revert:true,items:"> div.category",update:function(){return i()}});h();$("div.message").fadeIn("slow");$("input[name=timezone]").val(-(new Date().getTimezoneOffset()/60));o=$("#menu").tabs({active:0});u=$('#menu a[href="#'+e.displaytab+'"]').parent().index();if(u<0){u=0}o.tabs("option","active",u);z=$("#authMenu").tabs({active:0});if(e.choicetab){z.tabs("option","active",$('#authMenu a[href="#'+e.choicetab+'"]').parent().index())}if(e.login){$("input[type=password]:first").focus()}else{$("input[type!=hidden]:first").focus()}if(e.newwindow){$("#appslist a").attr("target","_blank")}if($("p.removeOther").length){x=$("form.login").attr("action");m=$("form.login").attr("method");v="";if(x.indexOf("?")!==-1){x.substring(0,x.indexOf("?"))+"?"}else{v=x+"?"}$("form.login input[type=hidden]").each(function(D){return v+="&"+$(this).attr("name")+"="+$(this).val()});q=$("p.removeOther a").attr("href")+"&method="+m+"&url="+btoa(v);$("p.removeOther a").attr("href",q)}if(navigator){t=[];s=[];l=[navigator.language];if(navigator.languages){l=navigator.languages}for(A=0,B=l.length;A<B;A++){n=l[A];p=window.availableLanguages;for(y=0,r=p.length;y<r;y++){w=p[y];if(w===n){t.push(w)}else{if(w.substring(0,1)===n.substring(0,1)){s.push(w)}}}}C=t[0]?t[0]:s[0]?s[0]:"en"}else{C="en"}if(e.pingInterval&&e.pingInterval>0){window.setTimeout(j,e.pingInterval)}$(".localeDate").each(function(){var D;D=new Date($(this).attr("val")*1000);return $(this).text(D.toLocaleString())});return k(C)})}).call(this);
(function(){var e,i,d,a,j,h,f,b,k,c,g=[].indexOf||function(o){for(var n=0,m=this.length;n<m;n++){if(n in this&&this[n]===o){return n}}return -1};c={};k=function(l){return $.getJSON(window.staticPrefix+"languages/"+l+".json",function(m){c=m;$("[trspan]").each(function(){return $(this).text(b($(this).attr("trspan")))});return $("[trmsg]").each(function(){return $(this).text(b("PE"+($(this).attr("trmsg"))))})})};b=function(l){if(c[l]){return c[l]}else{return l}};window.translate=b;d=function(){var l;l={};$("script[type='application/init']").each(function(){var q,n,m,p,o;try{o=JSON.parse($(this).text());p=[];for(m in o){p.push(l[m]=o[m])}return p}catch(n){q=n;console.log("Parsing error",q);return console.log("JSON",$(this).text())}});return l};f="#appslist";i=function(){return $.ajax({type:"POST",url:e.scriptname,data:{storeAppsListOrder:$(f).sortable("toArray").join()},dataType:"json"})};h=function(){var n,m,q,y,l,u,p,r,o,t,w,s,x;t=$(f);if(!((t!=null)&&e.appslistorder)){return null}n=e.appslistorder.split(",");u=t.sortable("toArray");w=[];for(q=0,r=u.length;q<r;q++){x=u[q];w[x]=x}for(p=0,o=n.length;p<o;p++){l=n[p];if(g.call(w,l)>=0){y=w[l];m=$(f+".ui-sortable").children("#"+y);s=$(f+".ui-sortable").children("#"+l);m.remove();$(f+".ui-sortable").filter(":first").append(s)}}return 1};a=function(l){return $("#lmhidden_"+l).length};j=function(){return $.ajax({type:"POST",url:e.scriptname,data:{ping:1},dataType:"json",success:function(l){if(l.auth){return setTimeout(j,e.pingInterval)}else{return location.reload(true)}}})};window.ping=j;e={};$(document).ready(function(){var x,w,z,v,A,y,C,t,s,B,r,q,u,o,m,n,l,p;e=d();window.datas=e;if(e.antiframe&&top!==self){top.location.href=location.href}$("#appslist").sortable({axis:"y",cursor:"move",opacity:0.5,revert:true,items:"> div.category",update:function(){return i()}});h();$("div.message").fadeIn("slow");$("input[name=timezone]").val(-(new Date().getTimezoneOffset()/60));o=$("#menu").tabs({active:0});u=$('#menu a[href="#'+e.displaytab+'"]').parent().index();if(u<0){u=0}o.tabs("option","active",u);z=$("#authMenu").tabs({active:0});if(e.choicetab){z.tabs("option","active",$('#authMenu a[href="#'+e.choicetab+'"]').parent().index())}if(e.login){$("input[type=password]:first").focus()}else{$("input[type!=hidden]:first").focus()}if(e.newwindow){$("#appslist a").attr("target","_blank")}if($("p.removeOther").length){x=$("form.login").attr("action");m=$("form.login").attr("method");v="";if(x.indexOf("?")!==-1){x.substring(0,x.indexOf("?"))+"?"}else{v=x+"?"}$("form.login input[type=hidden]").each(function(D){return v+="&"+$(this).attr("name")+"="+$(this).val()});q=$("p.removeOther a").attr("href")+"&method="+m+"&url="+btoa(v);$("p.removeOther a").attr("href",q)}if(navigator){t=[];s=[];l=[navigator.language];if(navigator.languages){l=navigator.languages}for(A=0,B=l.length;A<B;A++){n=l[A];p=window.availableLanguages;for(y=0,r=p.length;y<r;y++){w=p[y];if(w===n){t.push(w)}else{if(w.substring(0,1)===n.substring(0,1)){s.push(w)}}}}C=t[0]?t[0]:s[0]?s[0]:"en"}else{C="en"}if(e.pingInterval&&e.pingInterval>0){window.setTimeout(j,e.pingInterval)}$(".localeDate").each(function(){var D;D=new Date($(this).attr("val")*1000);return $(this).text(D.toLocaleString())});return k(C)})}).call(this);

View File

@ -5,25 +5,117 @@ LemonLDAP::NG U2F registration script
*/
(function() {
var register;
var register, setMsg, verify;
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);
};
register = function() {
var request;
request = [
{
challenge: window.datas.challenge,
version: 'U2F_V2'
return $.ajax({
type: "POST",
url: portal + "u2fregister/register",
data: {},
dataType: 'json',
error: function(j, status, err) {
return console.log('Error', err);
},
success: function(ch) {
var request;
request = [
{
challenge: ch.challenge,
version: ch.version
}
];
setMsg('touchU2fDevice', 'positive');
$('#u2fPermission').show();
return u2f.register(ch.appId, request, [], function(data) {
$('#u2fPermission').hide();
if (data.errorCode) {
return setMsg('unableToGetU2FKey', 'warning');
} else {
return $.ajax({
type: "POST",
url: portal + "u2fregister/registration",
data: {
registration: JSON.stringify(data)
},
dataType: 'json',
success: function(resp) {
if (resp.error) {
return setMsg('u2fFailed', 'warning');
} else if (resp.result) {
return setMsg('u2fSuccess', 'positive');
}
},
error: function(j, status, err) {
return console.log('error', err);
}
});
}
});
}
});
};
verify = function() {
return $.ajax({
type: "POST",
url: portal + "u2fregister/verify",
data: {},
dataType: 'json',
error: function(j, status, err) {
return console.log('Error', err);
},
success: function(ch) {
var request;
request = [
{
keyHandle: ch.keyHandle,
version: ch.version
}
];
setMsg('touchU2fDevice', 'positive');
return u2f.sign(ch.appId, ch.challenge, request, function(data) {
if (data.errorCode) {
return setMsg('unableToGetU2FKey', 'warning');
} else {
return $.ajax({
type: "POST",
url: portal + "u2fregister/signature",
data: {
signature: JSON.stringify(data)
},
dataType: 'json',
success: function(resp) {
if (resp.error) {
return setMsg('u2fFailed', 'warning');
} else if (resp.result) {
return setMsg('u2fSuccess', 'positive');
}
},
error: function(j, status, err) {
return console.log('error', err);
}
});
}
});
}
];
console.log('Register: ', request);
return u2f.register(window.datas.appId, request, [], function(data) {
$('#bind-data').val(JSON.stringify(data));
return $('#bind-form').submit();
});
};
$(document).ready(function() {
return setTimeout(register, 1000);
$('#u2fPermission').hide();
$('#register').on('click', register);
$('#verify').on('click', verify);
return $('#goback').attr('href', portal);
});
}).call(this);

View File

@ -1 +1 @@
(function(){var a;a=function(){var b;b=[{challenge:window.datas.challenge,version:"U2F_V2"}];console.log("Register: ",b);return u2f.register(window.datas.appId,b,[],function(c){$("#bind-data").val(JSON.stringify(c));return $("#bind-form").submit()})};$(document).ready(function(){return setTimeout(a,1000)})}).call(this);
(function(){var a,b,c;b=function(d,e){$("#msg").html(window.translate(d));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+e);if(e==="positive"){e="success"}return $("#color").addClass("alert-"+e)};a=function(){return $.ajax({type:"POST",url:portal+"u2fregister/register",data:{},dataType:"json",error:function(e,d,f){return console.log("Error",f)},success:function(d){var e;e=[{challenge:d.challenge,version:d.version}];b("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(d.appId,e,[],function(f){$("#u2fPermission").hide();if(f.errorCode){return b("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/registration",data:{registration:JSON.stringify(f)},dataType:"json",success:function(g){if(g.error){return b("u2fFailed","warning")}else{if(g.result){return b("u2fSuccess","positive")}}},error:function(h,g,i){return console.log("error",i)}})}})}})};c=function(){return $.ajax({type:"POST",url:portal+"u2fregister/verify",data:{},dataType:"json",error:function(e,d,f){return console.log("Error",f)},success:function(d){var e;e=[{keyHandle:d.keyHandle,version:d.version}];b("touchU2fDevice","positive");return u2f.sign(d.appId,d.challenge,e,function(f){if(f.errorCode){return b("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/signature",data:{signature:JSON.stringify(f)},dataType:"json",success:function(g){if(g.error){return b("u2fFailed","warning")}else{if(g.result){return b("u2fSuccess","positive")}}},error:function(h,g,i){return console.log("error",i)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",a);$("#verify").on("click",c);return $("#goback").attr("href",portal)})}).call(this);

View File

@ -175,6 +175,7 @@
"redirectionInProgres":"Redirection in progress...",
"redirectionToIdp":"Redirection to your Identity Provider",
"refuse":"Refuse",
"register": "Register",
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"requestIssuedFromIP":"The request was issued from IP",
@ -190,7 +191,9 @@
"touchU2fDevice": "Please touch the flashing U2F device now.",
"u2fFailed": "U2F verification failed. Retry or contact your administrator",
"u2fPermission": "You may be prompted to allow the site permission to access your security keys. After granting permission, the device will start to blink.",
"u2fSuccess": "Your key is registered",
"u2fRegistered": "Your key is registered",
"u2fSuccess": "Your key is successfully tested",
"unableToGetU2FKey": "Unable to access to your key. Retry or contact your administrator",
"updateCdc": "Update Common Domain Cookie",
"user":"User",
"useYubikey":"use your Yubikey",

View File

@ -175,6 +175,7 @@
"redirectionInProgres":"Redirection en cours...",
"redirectionToIdp":"Redirection vers votre fournisseur d'identité",
"refuse":"Refuser",
"register": "Enregistrer",
"registerRequestAlreadyIssued":"Une demande de création pour ce compte a déjà été faite le ",
"rememberChoice":"Se souvenir de mon choix",
"requestIssuedFromIP":"La demande provient de l'IP",
@ -190,7 +191,9 @@
"touchU2fDevice": "Poser votre doigt sur le périphérique U2F",
"u2fFailed": "La vérification U2F a échoué, réessayez ou contactez votre administrateur",
"u2fPermission": "Il est possible qu'on vous demande d'autoriser le site à accéder à votre clef. Après votre accord, la clef clignotera.",
"u2fSuccess": "Votre clef est enregistrée",
"u2fRegistered": "Votre clef est enregistrée. Cliquez sur vérifier",
"u2fSuccess": "Votre clef est vérifiée",
"unableToGetU2FKey": "Impossible d'accéder à la clef, réessayez ou contactez votre administrateur",
"updateCdc": "Mise à jour du cookie de domaine commun",
"user":"Utilisateur",
"useYubikey":"utilisez votre Yubikey",

View File

@ -1,17 +1,23 @@
<TMPL_INCLUDE NAME="header.tpl">
<TMPL_IF NAME="FAILED">
<div class="message message-warning alert"><span trspan="u2fFailed"></span></div>
</TMPL_IF>
<div id="color" class="message message-positive alert"><span id="msg" trspan=""></span></div>
<p id="u2fPermission" trspan="u2fPermission">You may be prompted to allow the site permission to access your security keys. After granting permission, the device will start to blink.</p>
<div class="buttons">
<span id="register" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="register">Register</span>
</span>
<span id="verify" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="verify">Verify</span>
</span>
<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>
<TMPL_IF NAME="CHALLENGE">
<div class="message message-positive alert"><span trspan="touchU2fDevice"></span></div>
<p trspan="u2fPermission">You may be prompted to allow the site permission to access your security keys. After granting permission, the device will start to blink.</p>
<form id="bind-form" action="#" method="post">
<input type="hidden" id="bind-data" name="registration" value="">
</form>
<script type="application/init">
<TMPL_VAR NAME="CHALLENGE">
</script>
<!-- //if:jsminified
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/u2f-api.min.js"></script>
@ -20,17 +26,4 @@
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/u2f-api.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/u2fregistration.js"></script>
<!-- //endif -->
</TMPL_IF>
<TMPL_IF NAME="SUCCESS">
<div class="message message-positive alert"><span trspan="u2fSuccess"></span></div>
</TMPL_IF>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
<TMPL_INCLUDE NAME="footer.tpl">