TOTP Portal part seems finished (#1359)

TODO: Manager attributes
This commit is contained in:
Xavier Guimard 2018-02-20 22:58:20 +01:00
parent afa0f1d6df
commit b9e2e91844
23 changed files with 201 additions and 29 deletions

View File

@ -24,8 +24,8 @@ portalSkin = bootstrap
staticPrefix = /static
languages = fr, en, vi, it, ar
templateDir = __pwd__/lemonldap-ng-portal/site/templates
;u2fActivation = 1
;u2fSelfRegistration = 1
;totp2fActivation = 1
;totpSelfRegistration = 1
[handler]

View File

@ -48,7 +48,7 @@ sub _code {
sub newSecret {
my ($self) = @_;
my @chars = ( 'a' .. 'z', 2 .. 7 );
return join( '', @chars[ map { int( rand(32) ) } 1 .. 16 ] );
return join( '', @chars[ map { int( rand(32) ) } 1 .. 32 ] );
}
1;

View File

@ -66,7 +66,7 @@ sub selfRegister {
my $code = $req->param('code');
unless ($code) {
$self->logger->userInfo('TOTP registration: empty validation form');
return $self->p->sendError( $req, 'missingCode', 400 );
return $self->p->sendError( $req, 'missingCode', 200 );
}
my $r = $self->verifyCode(
$self->conf->{totp2fInterval},
@ -89,7 +89,7 @@ sub selfRegister {
# 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');
$self->userLogger->notice('TOTP registration succeed');
return [ 200, [ 'Content-Type' => 'application/json' ],
['{"result":1}'] ];
}

View File

@ -33,11 +33,12 @@ sub init {
sub run {
my ( $self, $req, $token ) = @_;
$self->logger->debug('Generate TOTP form');
# Prepare form
my $tmp = $self->p->sendHtml(
$req,
'ext2fcheck',
'totp2fcheck',
params => {
SKIN => $self->conf->{portalSkin},
TOKEN => $token
@ -51,6 +52,7 @@ sub run {
sub verify {
my ( $self, $req, $session ) = @_;
$self->logger->debug('TOTP verification');
my $code;
unless ( $code = $req->param('code') ) {
$self->userLogger->error('TOTP 2F: no code');

View File

@ -188,9 +188,12 @@ sub display {
}
# 2.3 Case : user authenticated but an error was returned (bas url,...)
elsif ( not $req->datas->{noerror}
and $req->userData
and %{ $req->userData } )
elsif (
$req->noLoginDisplay
or ( not $req->datas->{noerror}
and $req->userData
and %{ $req->userData } )
)
{
$skinfile = 'error';
%templateParams = (

View File

@ -16,8 +16,10 @@ our @pList = (
portalDisplayResetPassword => '::Plugins::MailReset',
portalStatus => '::Plugins::Status',
cda => '::Plugins::CDA',
u2fActivation => '::2F::U2F',
ext2fActivation => '::2F::External2F',
totp2fActivation => '::2F::TOTP',
totpSelfRegistration => '::2F::Register::TOTP',
u2fActivation => '::2F::U2F',
u2fSelfRegistration => '::2F::Register::U2F',
notification => '::Plugins::Notifications',
portalCheckLogins => '::Plugins::History',

View File

@ -37,6 +37,10 @@ has customParameters => ( is => 'rw' );
# Boolean to indicate that response must be a redirection
has mustRedirect => ( is => 'rw' );
# Boolean to indicate that login form must not be displayed (used to reset
# authentication)
has noLoginDisplay => ( is => 'rw' );
# Store URL for redirections
has urldc => ( is => 'rw' );
has postUrl => ( is => 'rw' );

View File

@ -34,7 +34,8 @@ has prefix => ( is => 'rw' );
sub init {
my ($self) = @_;
$self->addUnauthRoute( $self->prefix . '2fcheck', '_verify', ['POST'] );
$self->addUnauthRoute( $self->prefix . '2fcheck' => '_verify', ['POST'] );
$self->addUnauthRoute( $self->prefix . '2fcheck' => '_redirect', ['GET'] );
my $rule = $self->conf->{ $self->prefix . '2fActivation' };
$rule = $self->p->HANDLER->substitute($rule);
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
@ -46,6 +47,14 @@ sub init {
1;
}
sub _redirect {
my ( $self, $req ) = @_;
my $arg = $req->env->{QUERY_STRING};
return [
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
];
}
sub _run {
my ( $self, $req ) = @_;
return PE_OK unless ( $self->rule->( $req, $req->sessionInfo ) );
@ -70,6 +79,7 @@ sub _verify {
my $token;
unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->prefix . ' 2F access without token' );
$req->mustRedirect(1);
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
}
@ -84,6 +94,7 @@ sub _verify {
# Case error
if ($res) {
$req->noLoginDisplay(1);
return $self->p->do( $req, [ sub { $res } ] );
}

View File

@ -23,12 +23,12 @@ getKey = (reset) ->
$.ajax
type: "POST",
url: "#{portal}/totpregister/getkey"
dataType: 'json'
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
@ -42,8 +42,34 @@ getKey = (reset) ->
if data.newkey
setMsg 'yourNewTotpKey', 'warning'
else
setMsg 'yourTotpKey', 'info'
setMsg 'yourTotpKey', 'success'
token = data.token
verify = ->
val = $('#code').val()
unless val
setMsg 'fillTheForm', 'danger'
else
$.ajax
type: "POST",
url: "#{portal}/totpregister/verify"
dataType: 'json'
data:
token: token
code: val
error: displayError
success: (data) ->
if data.error
if data.error.match /badCode/
setMsg 'badCode', 'warning'
else
setMsg data.error, 'danger'
else
setMsg 'yourKeyIsRegistered', 'success'
$(document).ready ->
getKey(0)
$('#changekey').on 'click', () ->
getKey(1)
$('#verify').on 'click', () ->
verify()

View File

@ -5,7 +5,7 @@ LemonLDAP::NG TOTP registration script
*/
(function() {
var displayError, getKey, setMsg, token;
var displayError, getKey, setMsg, token, verify;
setMsg = function(msg, level) {
$('#msg').html(window.translate(msg));
@ -34,13 +34,13 @@ LemonLDAP::NG TOTP registration script
return $.ajax({
type: "POST",
url: portal + "/totpregister/getkey",
dataType: 'json',
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'),
@ -51,15 +51,51 @@ LemonLDAP::NG TOTP registration script
if (data.newkey) {
setMsg('yourNewTotpKey', 'warning');
} else {
setMsg('yourTotpKey', 'info');
setMsg('yourTotpKey', 'success');
}
return token = data.token;
}
});
};
verify = function() {
var val;
val = $('#code').val();
if (!val) {
return setMsg('fillTheForm', 'danger');
} else {
return $.ajax({
type: "POST",
url: portal + "/totpregister/verify",
dataType: 'json',
data: {
token: token,
code: val
},
error: displayError,
success: function(data) {
if (data.error) {
if (data.error.match(/badCode/)) {
return setMsg('badCode', 'warning');
} else {
return setMsg(data.error, 'danger');
}
} else {
return setMsg('yourKeyIsRegistered', 'success');
}
}
});
}
};
$(document).ready(function() {
return getKey(0);
getKey(0);
$('#changekey').on('click', function() {
return getKey(1);
});
return $('#verify').on('click', function() {
return verify();
});
});
}).call(this);

View File

@ -1 +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);
(function(){var a,b,d,c,e;d=function(f,g){$("#msg").html(window.translate(f));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+g);if(g==="positive"){g="success"}return $("#color").addClass("alert-"+g)};a=function(g,f,i){var h;console.log("Error",i);h=JSON.parse(g.responseText);if(h&&h.error){h=h.error.replace(/.* /,"");console.log("Returned error",h);return d(h,"warning")}};c="";b=function(f){return $.ajax({type:"POST",url:portal+"/totpregister/getkey",dataType:"json",data:{newkey:f},error:a,success:function(i){var g,h;h="otpauth://totp/"+(escape(i.portal))+":"+(escape(i.user))+"?secret="+i.secret+"&issuer="+(escape(i.portal));g=new QRious({element:document.getElementById("qr"),value:h,size:150});$("#serialized").text(h);if(i.newkey){d("yourNewTotpKey","warning")}else{d("yourTotpKey","success")}return c=i.token}})};e=function(){var f;f=$("#code").val();if(!f){return d("fillTheForm","danger")}else{return $.ajax({type:"POST",url:portal+"/totpregister/verify",dataType:"json",data:{token:c,code:f},error:a,success:function(g){if(g.error){if(g.error.match(/badCode/)){return d("badCode","warning")}else{return d(g.error,"danger")}}else{return d("yourKeyIsRegistered","success")}}})}};$(document).ready(function(){b(0);$("#changekey").on("click",function(){return b(1)});return $("#verify").on("click",function(){return e()})})}).call(this);

View File

@ -98,6 +98,7 @@
"autoAccept":"تقبل تلقائيا في 30 ثانية",
"back2CasUrl":"التطبيق الذي قمت بتسجيل الخروج منه للتو قد وفرت وصلة قد ترغب في أن تتبعها",
"back2Portal":"العودة إلى البوابة",
"badCode":"Bad code",
"cancel":"إلغاء",
"captcha":"كلمة التحقق أو الكابتشا ",
"changeKey": "Generate new key",
@ -120,7 +121,9 @@
"enterExt2fCode":"تم إرسال رمز إليك. الرجاء إدخاله",
"enterOpenIDLogin":"الرجاء إدخال تسجيل الدخول الأوبين إيدي الخاص بك",
"enterYubikey":"يرجى استخدام يوبي كي الخاص بك",
"enterTotpCode":"Enter TOTP code",
"errorMsg":"رسالة خاطئة",
"fillTheForm":"Fill the form",
"firstName":"الاسم الاول",
"forgotPwd":"نسيت كلمة المرور؟",
"generatePwd":"إنشاء كلمة المرور تلقائيا",
@ -211,6 +214,9 @@
"yourEmail":"بريدك الالكتروني",
"yourIdentity":"هويتك",
"yourIdentityIs":"هويتك هي",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"رقم هاتفك",
"yourProfile":"ملفك الشخصي"
"yourProfile":"ملفك الشخصي",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Acceptation automatique dans 30 secondes",
"back2CasUrl":"Le service duquel vous arrivez a fourni un lien que vous êtes invité à suivre",
"back2Portal":"Retourner au portail",
"badCode":"Mauvais code",
"cancel":"Annuler",
"captcha":"Captcha",
"changeKey": "Générer une nouvelle clef",
@ -119,8 +120,10 @@
"enterCred":"Merci de vous authentifier",
"enterExt2fCode":"Un code vous a été envoyé, entrez-le ici",
"enterOpenIDLogin":"Entrez votre identifiant OpenID",
"enterTotpCode":"Entrez le code TOTP",
"enterYubikey":"Utilisez votre Yubikey",
"errorMsg":"Message d'erreur",
"fillTheForm":"Remplissez le formulaire",
"firstName":"Prénom",
"forgotPwd":"Mot de passe oublié ?",
"generatePwd":"Générer le mot de passe automatiquement",
@ -211,6 +214,9 @@
"yourEmail":"Votre adresse électronique",
"yourIdentity":"Votre identité",
"yourIdentityIs":"Votre identité est",
"yourKeyIsRegistered":"Votre clef est enregistrée",
"yourNewTotpKey":"Votre nouvelle clef TOTP, testez-la et entrez le code",
"yourPhone":"Votre numéro de téléphone",
"yourProfile":"Vos informations personnelles"
"yourProfile":"Vos informations personnelles",
"yourTotpKey":"Votre clef TOTP"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Accetta automaticamente in 30 secondi",
"back2CasUrl":"L'applicazione dalla quale ti sei appena sconnesso ha fornito un link che dovresti seguire",
"back2Portal":"Torna al portale",
"badCode":"Bad code",
"cancel":"Cancella",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Inserisci le tue credenziali",
"enterExt2fCode":"Un codice vi é stato inviato. Inseritelo",
"enterOpenIDLogin":"Inserisci il tuo login OpenID",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Utilizza il tuo Yubikey",
"errorMsg":"Messaggio di errore",
"fillTheForm":"Fill the form",
"firstName":"Nome",
"forgotPwd":"Password dimenticata?",
"generatePwd":"Generare automaticamente la password",
@ -211,6 +214,9 @@
"yourEmail":"E-mail",
"yourIdentity":"Identità",
"yourIdentityIs":"La tua identità é",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Numero di telefono",
"yourProfile":"Il tuo profilo"
"yourProfile":"Il tuo profilo",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Automatically accept in 30 seconds",
"back2CasUrl":"The application you just logged out of has provided a link it would like you to follow",
"back2Portal":"Go back to portal",
"badCode":"Bad code",
"cancel":"Cancel",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Please enter your credentials",
"enterExt2fCode":"A code has been sent to you. Please enter it",
"enterOpenIDLogin":"Please enter your OpenID login",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forgotPwd":"Forgot your password?",
"generatePwd":"Generate the password automatically",
@ -211,6 +214,9 @@
"yourEmail":"Your email",
"yourIdentity":"Your identity",
"yourIdentityIs":"Your identity is",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Your phone number",
"yourProfile":"Your profile"
"yourProfile":"Your profile",
"yourTotpKey":"Your TOTP key"
}

View File

@ -98,6 +98,7 @@
"autoAccept":"Tự động chấp nhận trong 30 giây",
"back2CasUrl":"Ứng dụng bạn vừa đăng xuất đã cung cấp một liên kết mà bạn muốn theo dõi",
"back2Portal":"Quay lại cổng thông tin",
"badCode":"Bad code",
"cancel":"Hủy",
"captcha":"Captcha",
"changeKey": "Generate new key",
@ -119,8 +120,10 @@
"enterCred":"Vui lòng nhập thông tin đăng nhập của bạn",
"enterExt2fCode":"Một mã đã được gửi cho bạn. Hãy nhập nó",
"enterOpenIDLogin":"Hãy nhập thông tin đăng nhập OpenID của bạn",
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Vui lòng sử dụng Yubikey của bạn",
"errorMsg":"Thông báo lỗi",
"fillTheForm":"Fill the form",
"firstName":"Tên",
"forgotPwd":"Quên mật khẩu của bạn?",
"generatePwd":"Tạo mật khẩu tự động",
@ -211,6 +214,9 @@
"yourEmail":"Email của bạn",
"yourIdentity":"Nhận dạng của bạn",
"yourIdentityIs":"Nhận dạng của bạn là",
"yourKeyIsRegistered":"Your key is registered",
"yourNewTotpKey":"Your new TOTP key, please test it and enter the code",
"yourPhone":"Số điện thoại của bạn",
"yourProfile":"Profile của bạn"
"yourProfile":"Profile của bạn",
"yourTotpKey":"Your TOTP key"
}

View File

@ -0,0 +1,28 @@
<TMPL_INCLUDE NAME="header.tpl">
<main id="logincontent" class="container">
<div class="message message-positive alert"><span trspan="enterTotpCode"></span></div>
<div class="panel panel-default">
<form action="/totp2fcheck" method="post" class="password" role="form">
<div class="form">
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">">
<div class="form-group input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i> </span>
<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">
</div>
</div>
<div class="buttons">
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-log-in"></span>
<span trspan="connect">Connect</span>
</button>
</div>
</div>
</main>
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -13,7 +13,7 @@
</pre>
</div>
<div class="form-group">
<input name="code" type="number" />
<input id="code" name="code" type="number" />
</div>
<div class="buttons">
<span id="changekey" class="btn btn-success" role="button">