diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index a9b0d558..4070184c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -30,6 +30,8 @@ sub noLoginNeeded { return $whitelisted || $c->request->path eq "api/push-github" || $c->request->path eq "google-login" || + $c->request->path eq "github-redirect" || + $c->request->path eq "github-login" || $c->request->path eq "login" || $c->request->path eq "logo" || $c->request->path =~ /^static\//; diff --git a/src/lib/Hydra/Controller/User.pm b/src/lib/Hydra/Controller/User.pm index 656ce018..9cdece8a 100644 --- a/src/lib/Hydra/Controller/User.pm +++ b/src/lib/Hydra/Controller/User.pm @@ -4,6 +4,7 @@ use utf8; use strict; use warnings; use base 'Hydra::Base::Controller::REST'; +use File::Slurp; use Crypt::RandPasswd; use Digest::SHA1 qw(sha1_hex); use Hydra::Helper::Nix; @@ -154,6 +155,67 @@ sub google_login :Path('/google-login') Args(0) { doEmailLogin($self, $c, "google", $data->{email}, $data->{name} // undef); } +sub github_login :Path('/github-login') Args(0) { + my ($self, $c) = @_; + + my $client_id = $c->config->{github_client_id} or die "github_client_id not configured."; + my $client_secret = $c->config->{github_client_secret} // do { + my $client_secret_file = $c->config->{github_client_secret_file} or die "github_client_secret nor github_client_secret_file is configured."; + my $client_secret = read_file($client_secret_file); + $client_secret =~ s/\s+//; + $client_secret; + }; + die "No github secret configured" unless $client_secret; + + my $ua = new LWP::UserAgent; + my $response = $ua->post( + 'https://github.com/login/oauth/access_token', + { + client_id => $client_id, + client_secret => $client_secret, + code => ($c->req->params->{code} // die "No token."), + }, Accept => 'application/json'); + error($c, "Did not get a response from GitHub.") unless $response->is_success; + + my $data = decode_json($response->decoded_content) or die; + my $access_token = $data->{access_token} // die "No access_token in response from GitHub."; + + $response = $ua->get('https://api.github.com/user/emails', Accept => 'application/vnd.github.v3+json', Authorization => "token $access_token"); + error($c, "Did not get a response from GitHub for email info.") unless $response->is_success; + + $data = decode_json($response->decoded_content) or die; + my $email; + + foreach my $eml (@{$data}) { + $email = $eml->{email} if $eml->{verified} && $eml->{primary}; + } + + die "No primary email for this GitHub profile" unless $email; + + $response = $ua->get('https://api.github.com/user', Authorization => "token $access_token"); + error($c, "Did not get a response from GitHub for user info.") unless $response->is_success; + $data = decode_json($response->decoded_content) or die; + + doEmailLogin($self, $c, "github", $email, $data->{name} // undef); + + $c->res->redirect($c->uri_for($c->res->cookies->{'after_github'})); +} + +sub github_redirect :Path('/github-redirect') Args(0) { + my ($self, $c) = @_; + + my $client_id = $c->config->{github_client_id} or die "github_client_id not configured."; + + my $after = "/" . $c->req->params->{after}; + + $c->res->cookies->{'after_github'} = { + name => 'after_github', + value => $after, + }; + + $c->res->redirect("https://github.com/login/oauth/authorize?client_id=$client_id&scope=user:email"); +} + sub captcha :Local Args(0) { my ($self, $c) = @_; diff --git a/src/root/topbar.tt b/src/root/topbar.tt index b9b839f9..f8366917 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -136,6 +136,10 @@
  • Sign in with Google
  • [% END %] + [% IF c.config.github_client_id %] +
  • Sign in with GitHub
  • +
  • + [% END %]
  • Sign in with a Hydra account
  • diff --git a/src/script/hydra-create-user b/src/script/hydra-create-user index 8d673a05..3fde1aad 100755 --- a/src/script/hydra-create-user +++ b/src/script/hydra-create-user @@ -11,7 +11,7 @@ sub showHelp { print < \$renameFrom, die "$0: one user name required\n" if scalar @ARGV != 1; my $userName = $ARGV[0]; -die "$0: type must be `hydra' or `google'\n" - if defined $type && $type ne "hydra" && $type ne "google"; +die "$0: type must be `hydra', `google' or `github'\n" + if defined $type && $type ne "hydra" && $type ne "google" && $type ne "github"; my $db = Hydra::Model::DB->new(); @@ -67,19 +67,19 @@ $db->txn_do(sub { { username => $userName, type => "hydra", emailaddress => "", password => "!" }); } - die "$0: Google user names must be email addresses\n" - if $user->type eq "google" && $userName !~ /\@/; + die "$0: Google or GitHub user names must be email addresses\n" + if ($user->type eq "google" || $user->type eq "github") && $userName !~ /\@/; $user->update({ type => $type }) if defined $type; $user->update({ fullname => $fullName eq "" ? undef : $fullName }) if defined $fullName; - if ($user->type eq "google") { - die "$0: Google accounts do not have an explicitly set email address.\n" + if ($user->type eq "google" || $user->type eq "github") { + die "$0: Google and GitHub accounts do not have an explicitly set email address.\n" if defined $emailAddress; - die "$0: Google accounts do not have a password.\n" + die "$0: Google and GitHub accounts do not have a password.\n" if defined $password; - die "$0: Google accounts do not have a password.\n" + die "$0: Google and GitHub accounts do not have a password.\n" if defined $passwordHash; $user->update({ emailaddress => $userName, password => "!" }); } else { diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 67843a90..46e3827e 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -10,7 +10,7 @@ create table Users ( emailAddress text not null, password text not null, -- sha256 hash emailOnError integer not null default 0, - type text not null default 'hydra', -- either "hydra" or "google" + type text not null default 'hydra', -- either "hydra", "google" or "github" publicDashboard boolean not null default false );