forked from lix-project/hydra
Add support for logging in via a Google account
The required configuration in hydra.conf: enable_google_login = 1 google_client_id = 238429sdjkds....apps.googleusercontent.com and optionally persona_allowed_domains to restrict to one or more domains.
This commit is contained in:
parent
f11ce7e219
commit
5a580b1bb2
|
@ -117,6 +117,7 @@ rec {
|
||||||
CatalystViewJSON
|
CatalystViewJSON
|
||||||
CatalystViewTT
|
CatalystViewTT
|
||||||
CatalystXScriptServerStarman
|
CatalystXScriptServerStarman
|
||||||
|
CryptJWT
|
||||||
CryptRandPasswd
|
CryptRandPasswd
|
||||||
DBDPg
|
DBDPg
|
||||||
DBDSQLite
|
DBDSQLite
|
||||||
|
|
|
@ -14,10 +14,12 @@ use JSON;
|
||||||
# Put this controller at top-level.
|
# Put this controller at top-level.
|
||||||
__PACKAGE__->config->{namespace} = '';
|
__PACKAGE__->config->{namespace} = '';
|
||||||
|
|
||||||
|
|
||||||
sub noLoginNeeded {
|
sub noLoginNeeded {
|
||||||
my ($c) = @_;
|
my ($c) = @_;
|
||||||
|
|
||||||
return $c->request->path eq "persona-login" ||
|
return $c->request->path eq "persona-login" ||
|
||||||
|
$c->request->path eq "google-login" ||
|
||||||
$c->request->path eq "login" ||
|
$c->request->path eq "login" ||
|
||||||
$c->request->path eq "logo" ||
|
$c->request->path eq "logo" ||
|
||||||
$c->request->path =~ /^static\//;
|
$c->request->path =~ /^static\//;
|
||||||
|
@ -35,7 +37,6 @@ sub begin :Private {
|
||||||
$c->stash->{tracker} = $ENV{"HYDRA_TRACKER"};
|
$c->stash->{tracker} = $ENV{"HYDRA_TRACKER"};
|
||||||
$c->stash->{flashMsg} = $c->flash->{flashMsg};
|
$c->stash->{flashMsg} = $c->flash->{flashMsg};
|
||||||
$c->stash->{successMsg} = $c->flash->{successMsg};
|
$c->stash->{successMsg} = $c->flash->{successMsg};
|
||||||
$c->stash->{personaEnabled} = $c->config->{enable_persona} // "0" eq "1";
|
|
||||||
|
|
||||||
$c->stash->{isPrivateHydra} = $c->config->{private} // "0" ne "0";
|
$c->stash->{isPrivateHydra} = $c->config->{private} // "0" ne "0";
|
||||||
|
|
||||||
|
@ -69,6 +70,7 @@ sub begin :Private {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub deserialize :ActionClass('Deserialize') { }
|
sub deserialize :ActionClass('Deserialize') { }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use utf8;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use base 'Hydra::Base::Controller::REST';
|
use base 'Hydra::Base::Controller::REST';
|
||||||
|
use Crypt::JWT qw(decode_jwt);
|
||||||
use Crypt::RandPasswd;
|
use Crypt::RandPasswd;
|
||||||
use Digest::SHA1 qw(sha1_hex);
|
use Digest::SHA1 qw(sha1_hex);
|
||||||
use Hydra::Helper::Nix;
|
use Hydra::Helper::Nix;
|
||||||
|
@ -45,11 +46,63 @@ sub logout_POST {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub doEmailLogin {
|
||||||
|
my ($self, $c, $type, $email, $fullName) = @_;
|
||||||
|
|
||||||
|
die "No email address provided.\n" unless defined $email;
|
||||||
|
|
||||||
|
# Be paranoid about the email address format, since we do use it
|
||||||
|
# in URLs.
|
||||||
|
die "Illegal email address.\n" unless $email =~ /^[a-zA-Z0-9\.\-\_]+@[a-zA-Z0-9\.\-\_]+$/;
|
||||||
|
|
||||||
|
# If persona_allowed_domains is set, check if the email address
|
||||||
|
# returned is on these domains. When not configured, allow all
|
||||||
|
# domains.
|
||||||
|
my $allowed_domains = $c->config->{persona_allowed_domains} || "";
|
||||||
|
if ($allowed_domains ne "") {
|
||||||
|
my $email_ok = 0;
|
||||||
|
my @domains = split ',', $allowed_domains;
|
||||||
|
map { $_ =~ s/^\s*(.*?)\s*$/$1/ } @domains;
|
||||||
|
|
||||||
|
foreach my $domain (@domains) {
|
||||||
|
$email_ok = $email_ok || ((split '@', $email)[1] eq $domain);
|
||||||
|
}
|
||||||
|
error($c, "Your email address does not belong to a domain that is allowed to log in.\n")
|
||||||
|
unless $email_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $user = $c->find_user({ username => $email });
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
# Automatically upgrade Persona accounts to Google accounts.
|
||||||
|
if ($user->type eq "persona" && $type eq "google") {
|
||||||
|
$user->update({type => "google"});
|
||||||
|
}
|
||||||
|
|
||||||
|
die "You cannot login via login type '$type'.\n" if $user->type ne $type;
|
||||||
|
} else {
|
||||||
|
$c->model('DB::Users')->create(
|
||||||
|
{ username => $email
|
||||||
|
, fullname => $fullName,
|
||||||
|
, password => "!"
|
||||||
|
, emailaddress => $email,
|
||||||
|
, type => $type
|
||||||
|
});
|
||||||
|
$user = $c->find_user({ username => $email }) or die;
|
||||||
|
}
|
||||||
|
|
||||||
|
$c->set_authenticated($user);
|
||||||
|
|
||||||
|
$self->status_no_content($c);
|
||||||
|
$c->flash->{successMsg} = "You are now signed in as <tt>" . encode_entities($email) . "</tt>.";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sub persona_login :Path('/persona-login') Args(0) {
|
sub persona_login :Path('/persona-login') Args(0) {
|
||||||
my ($self, $c) = @_;
|
my ($self, $c) = @_;
|
||||||
requirePost($c);
|
requirePost($c);
|
||||||
|
|
||||||
error($c, "Persona support is not enabled.") unless $c->stash->{personaEnabled};
|
error($c, "Logging in via Persona is not enabled.") unless $c->config->{enable_persona};
|
||||||
|
|
||||||
my $assertion = $c->stash->{params}->{assertion} or die;
|
my $assertion = $c->stash->{params}->{assertion} or die;
|
||||||
|
|
||||||
|
@ -64,42 +117,54 @@ sub persona_login :Path('/persona-login') Args(0) {
|
||||||
my $d = decode_json($response->decoded_content) or die;
|
my $d = decode_json($response->decoded_content) or die;
|
||||||
error($c, "Persona says: $d->{reason}") if $d->{status} ne "okay";
|
error($c, "Persona says: $d->{reason}") if $d->{status} ne "okay";
|
||||||
|
|
||||||
my $email = $d->{email} or die;
|
doEmailLogin($self, $c, "persona", $d->{email}, undef);
|
||||||
|
}
|
||||||
|
|
||||||
# Be paranoid about the email address format, since we do use it
|
|
||||||
# in URLs.
|
|
||||||
die "Illegal email address." unless $email =~ /^[a-zA-Z0-9\.\-\_]+@[a-zA-Z0-9\.\-\_]+$/;
|
|
||||||
|
|
||||||
# If persona_allowed_domains is set, check if the email address returned is on these domains.
|
# From https://www.googleapis.com/oauth2/v3/certs. Should probably not
|
||||||
# When not configured, allow all domains.
|
# hard-code this.
|
||||||
my $allowed_domains = $c->config->{persona_allowed_domains} || "";
|
my $googleKeys = <<'EOF';
|
||||||
if ( $allowed_domains ne "") {
|
{
|
||||||
my $email_ok = 0;
|
"keys": [
|
||||||
my @domains = split ',', $allowed_domains;
|
{
|
||||||
map { $_ =~ s/^\s*(.*?)\s*$/$1/ } @domains;
|
"kty": "RSA",
|
||||||
|
"alg": "RS256",
|
||||||
|
"use": "sig",
|
||||||
|
"kid": "10685afd5291883ce668345afd77201390406f82",
|
||||||
|
"n": "xeNopuszp35W6H1w2Tw4OrSwT8BZ9f7-2PoOyWZmfMmUDmYT2uxrZezDK0YLap5LVmpLNcpZP5Hj67_32NU3my4qfA-SlxuJMUxHWJF7Dqr-QNAqld0SZ_po4qz5ZTHDxNxoZ4iw_T-4lhIBGm0RIZprDDGPI7Vo8qIeIMjZywoh_nq32zB6tnjEUBvHcgay0qXEnQkKkavzHO_c5sLc1qXM0jDQVqyO1enevW2yA_8gP0Qb7014ycN5umCvEHc66c2_iNT-R4zgw8gd1g05n2xwyET8qb_3wi5LqUV-Cri4mJ2xwGY8uynlD2I4jVtOYJusBgNs6AfwyehzsLdwSQ",
|
||||||
|
"e": "AQAB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kty": "RSA",
|
||||||
|
"alg": "RS256",
|
||||||
|
"use": "sig",
|
||||||
|
"kid": "5a68fc8a3ec0c30e0be95aa08db99a68a725467f",
|
||||||
|
"n": "zmXvUwXYSo8VouhnkURp-3xywch-jPrk7q0gugqC7QIchBPnvdXdS-bj6sr1AqDl_hEDtiLGfiVr3Ft_U022rtHAl5n5NxyybUtZXWyT5yQZM4jopGBajavEUdCl9b4pqb-q_3fVaxUXe7re23sVjI5Bntd-8RYZ70tq-ZvCWBqsnz6lHi9Ditp3CZGWLMMBZlIv3nKnClOrZXL98Jmt7AAod-Gtk65saqnrMwWtBcI_Q-3u23ytywbMLanCeFFNUWlIOgZqyYYkOm-ylLRJzVaZ1THtcWILWCYUgxXjyF9DtXO3a8nct2JhdacD3LzRiPv3sXr31cg4arwUk19JoQ",
|
||||||
|
"e": "AQAB"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
foreach my $domain (@domains) {
|
|
||||||
$email_ok = $email_ok || ((split '@', $email)[1] eq $domain);
|
|
||||||
}
|
|
||||||
die "Email address is not allowed to login." unless $email_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $user = $c->find_user({ username => $email });
|
sub google_login :Path('/google-login') Args(0) {
|
||||||
|
my ($self, $c) = @_;
|
||||||
|
requirePost($c);
|
||||||
|
|
||||||
if (!$user) {
|
error($c, "Logging in via Google is not enabled.") unless $c->config->{enable_google_login};
|
||||||
$c->model('DB::Users')->create(
|
|
||||||
{ username => $email
|
|
||||||
, password => "!"
|
|
||||||
, emailaddress => $email,
|
|
||||||
, type => "persona"
|
|
||||||
});
|
|
||||||
$user = $c->find_user({ username => $email }) or die;
|
|
||||||
}
|
|
||||||
|
|
||||||
$c->set_authenticated($user);
|
my $data = decode_jwt(
|
||||||
|
token => ($c->stash->{params}->{id_token} // die "No token."),
|
||||||
|
kid_keys => $googleKeys,
|
||||||
|
verify_exp => 1,
|
||||||
|
);
|
||||||
|
|
||||||
$self->status_no_content($c);
|
die unless $data->{aud} eq $c->config->{google_client_id};
|
||||||
$c->flash->{successMsg} = "You are now signed in as <tt>" . encode_entities($email) . "</tt>.";
|
die unless $data->{iss} eq "accounts.google.com" || $data->{iss} eq "https://accounts.google.com";
|
||||||
|
die "Email address is not verified" unless $data->{email_verified};
|
||||||
|
# FIXME: verify hosted domain claim?
|
||||||
|
|
||||||
|
doEmailLogin($self, $c, "google", $data->{email}, $data->{name} // undef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
108
src/root/auth.tt
Normal file
108
src/root/auth.tt
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
[% IF c.user_exists %]
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function finishSignOut() {
|
||||||
|
$.post("[% c.uri_for('/logout') %]")
|
||||||
|
.done(function(data) {
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.fail(function() { bootbox.alert("Server request failed!"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function signOut() {
|
||||||
|
[% IF c.user.type == 'google' %]
|
||||||
|
gapi.load('auth2', function() {
|
||||||
|
gapi.auth2.init();
|
||||||
|
var auth2 = gapi.auth2.getAuthInstance();
|
||||||
|
auth2.then(function () {
|
||||||
|
auth2.signOut().then(finishSignOut, finishSignOut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
[% ELSE %]
|
||||||
|
finishSignOut();
|
||||||
|
[% END %]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
[% ELSE %]
|
||||||
|
|
||||||
|
<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">User name</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="span3" name="username" value=""/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Password</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" class="span3" name="password" value=""/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="do-signin" class="btn btn-primary">Sign in</button>
|
||||||
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function finishSignOut() { }
|
||||||
|
|
||||||
|
$("#do-signin").click(function() {
|
||||||
|
requestJSON({
|
||||||
|
url: "[% c.uri_for('/login') %]",
|
||||||
|
data: $(this).parents("form").serialize(),
|
||||||
|
type: 'POST',
|
||||||
|
success: function(data) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
[% IF c.config.enable_google_login %]
|
||||||
|
<script>
|
||||||
|
function onGoogleSignIn(googleUser) {
|
||||||
|
requestJSON({
|
||||||
|
url: "[% c.uri_for('/google-login') %]",
|
||||||
|
data: "id_token=" + googleUser.getAuthResponse().id_token,
|
||||||
|
type: 'POST',
|
||||||
|
success: function(data) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
[% END %]
|
||||||
|
|
||||||
|
[% END %]
|
||||||
|
|
||||||
|
[% IF c.config.enable_persona %]
|
||||||
|
<script src="https://login.persona.org/include.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
navigator.id.watch({
|
||||||
|
onlogin: function(assertion) {
|
||||||
|
requestJSON({
|
||||||
|
url: "[% c.uri_for('/persona-login') %]",
|
||||||
|
data: "assertion=" + assertion,
|
||||||
|
type: 'POST',
|
||||||
|
success: function(data) { window.location.reload(); },
|
||||||
|
postError: function() { navigator.id.logout(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#persona-signin").click(function() {
|
||||||
|
navigator.id.request({ siteName: 'Hydra' });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
[% END %]
|
|
@ -36,6 +36,11 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="[% c.uri_for("/static/js/common.js") %]"></script>
|
<script type="text/javascript" src="[% c.uri_for("/static/js/common.js") %]"></script>
|
||||||
|
|
||||||
|
[% IF c.config.enable_google_login %]
|
||||||
|
<meta name="google-signin-client_id" content="[% c.config.google_client_id %]">
|
||||||
|
<script src="https://apis.google.com/js/platform.js" async="1" defer="1"></script>
|
||||||
|
[% END %]
|
||||||
|
|
||||||
[% tracker %]
|
[% tracker %]
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
@ -94,95 +99,16 @@
|
||||||
<small>
|
<small>
|
||||||
<em><a href="http://nixos.org/hydra" target="_blank">Hydra</a> [% HTML.escape(version) %] (using [% HTML.escape(nixVersion) %]).</em>
|
<em><a href="http://nixos.org/hydra" target="_blank">Hydra</a> [% HTML.escape(version) %] (using [% HTML.escape(nixVersion) %]).</em>
|
||||||
[% IF c.user_exists %]
|
[% IF c.user_exists %]
|
||||||
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>[% IF c.user.type == 'persona' %] via Persona[% END %].
|
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>
|
||||||
|
[%- IF c.user.type == 'persona' %] via Persona
|
||||||
|
[%- ELSIF c.user.type == 'google' %] via Google[% END %].
|
||||||
[% END %]
|
[% END %]
|
||||||
</small>
|
</small>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
[% PROCESS auth.tt %]
|
||||||
function doLogout() {
|
|
||||||
[% IF c.user_exists %]
|
|
||||||
$.post("[% c.uri_for('/logout') %]")
|
|
||||||
.done(function(data) {
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
.fail(function() { bootbox.alert("Server request failed!"); });
|
|
||||||
[% END %]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
[% IF c.user_exists && c.user.type == 'hydra' %]
|
|
||||||
<script>
|
|
||||||
$("#persona-signout").click(doLogout);
|
|
||||||
</script>
|
|
||||||
[% ELSIF personaEnabled %]
|
|
||||||
<script src="https://login.persona.org/include.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
navigator.id.watch({
|
|
||||||
loggedInUser: [% c.user_exists ? '"' _ HTML.escape(c.user.username) _ '"' : "null" %],
|
|
||||||
onlogin: function(assertion) {
|
|
||||||
requestJSON({
|
|
||||||
url: "[% c.uri_for('/persona-login') %]",
|
|
||||||
data: "assertion=" + assertion,
|
|
||||||
type: 'POST',
|
|
||||||
success: function(data) { window.location.reload(); },
|
|
||||||
postError: function() { navigator.id.logout(); }
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onlogout: doLogout
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#persona-signin").click(function() {
|
|
||||||
navigator.id.request({ siteName: 'Hydra' });
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#persona-signout").click(function() {
|
|
||||||
navigator.id.logout();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
[% IF !c.user_exists %]
|
|
||||||
<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label">User name</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" class="span3" name="username" value=""/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label">Password</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="password" class="span3" name="password" value=""/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button id="do-signin" class="btn btn-primary">Sign in</button>
|
|
||||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$("#do-signin").click(function() {
|
|
||||||
requestJSON({
|
|
||||||
url: "[% c.uri_for('/login') %]",
|
|
||||||
data: $(this).parents("form").serialize(),
|
|
||||||
type: 'POST',
|
|
||||||
success: function(data) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -127,24 +127,26 @@
|
||||||
[% IF c.user_exists %]
|
[% IF c.user_exists %]
|
||||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('User').action_for('edit'), [c.user.username]) title = "Preferences" %]
|
[% INCLUDE menuItem uri = c.uri_for(c.controller('User').action_for('edit'), [c.user.username]) title = "Preferences" %]
|
||||||
<li>
|
<li>
|
||||||
<a href="#" id="persona-signout">Sign out</a>
|
<a href="#" onclick="signOut();">Sign out</a>
|
||||||
</li>
|
</li>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
[% IF personaEnabled %]
|
[% WRAPPER makeSubMenu title="Sign in" %]
|
||||||
[% WRAPPER makeSubMenu title="Sign in" %]
|
[% IF c.config.enable_google_login %]
|
||||||
|
<li>
|
||||||
|
<a><div class="g-signin2" data-onsuccess="onGoogleSignIn" data-theme="dark"></div></a>
|
||||||
|
</li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
[% END %]
|
||||||
|
[% IF c.config.enable_persona %]
|
||||||
<li>
|
<li>
|
||||||
<a href="#" id="persona-signin">
|
<a href="#" id="persona-signin">
|
||||||
<img src="[% c.uri_for("/static/images/persona_sign_in_blue.png") %]" alt="Sign in with Persona" />
|
<img src="[% c.uri_for("/static/images/persona_sign_in_blue.png") %]" alt="Sign in with Persona" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li>
|
|
||||||
<a href="#hydra-signin" data-toggle="modal">Sign in with a Hydra account</a>
|
|
||||||
</li>
|
|
||||||
[% END %]
|
[% END %]
|
||||||
[% ELSE %]
|
|
||||||
<li>
|
<li>
|
||||||
<a href="#hydra-signin" data-toggle="modal">Sign in</a>
|
<a href="#hydra-signin" data-toggle="modal">Sign in with a Hydra account</a>
|
||||||
</li>
|
</li>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">Email</label>
|
<label class="control-label">Email</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" class="span3" name="emailaddress" [% IF !create && user.type == 'persona' %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/>
|
<input type="text" class="span3" name="emailaddress" [% IF !create && user.username.search('@') %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue